Stub calls depending on the arguments

Cypress bundled Sinon.jsopen in new window that allows one to stub method calls depending on the argument. Here are a few examples, for more see the Spies, Stubs & Clocks.

Stub all calls

Imagine we have an object with the method x that doubles any value given to it.

const doubler = {
  double(x) {
    return x + x
  },
}

We can test its actual behavior

expect(doubler.double(10), '10 x2').to.equal(20)

We can stub the method and any call would return the same mocked value

cy.stub(doubler, 'double').returns(42)
expect(doubler.double(10), '10 x2').to.equal(42)
expect(doubler.double('fun'), 'fun x2').to.equal(42)

We can confirm the number of times the stubbed method was called

expect(doubler.double).to.have.been.calledTwice
// equivalent way of checking the number of calls
expect(doubler.double).to.have.property('callCount', 2)

And we can confirm the call arguments

expect(doubler.double).to.have.been.calledWith(10)
expect(doubler.double).to.have.been.calledWith('fun')
// an example of a negative assertion
expect(doubler.double).to.not.have.been.calledWith(-5)

We can reset the call history

doubler.double.resetHistory()
expect(doubler.double).to.not.be.called
expect(doubler.double(3)).to.equal(42)
expect(doubler.double).to.have.been.calledOnce

We can restore the default method behavior

doubler.double.restore()
expect(doubler.double('fun')).to.equal('funfun')

Stub calls by order

We can respond differently to different method calls. For example, we can return 1 on the first call, 2 on the second

const doubler = {
  double(x) {
    return x + x
  },
}

cy.stub(doubler, 'double')
  .onFirstCall()
  .returns(1)
  .onSecondCall()
  .returns(2)
expect(doubler.double('first')).to.equal(1)
expect(doubler.double('second')).to.equal(2)
expect(doubler.double).to.have.been.calledTwice

All unspecified calls will get undefined result

expect(doubler.double('third')).to.equal(undefined)

Stub plus callThrough

If you want to stub some calls, but call the original method for all other calls, use callThrough

const doubler = {
  double(x) {
    return x + x
  },
}

cy.stub(doubler, 'double')
  // all calls should execute the original method
  .callThrough()
  // except the first call that should return 1
  .onFirstCall()
  .returns(1)
  // and the second call that should return 2
  .onSecondCall()
  .returns(2)
expect(doubler.double('first')).to.equal(1)
expect(doubler.double('second')).to.equal(2)
expect(doubler.double).to.have.been.calledTwice

All calls after the first two execute the original doubler.double method

// the original method is called since the stub no longer applies
expect(doubler.double('third')).to.equal('thirdthird')
expect(doubler.double(4)).to.equal(8)

Note that the stub counter is still incremented and we can inspect the calls

// the stub count is incremented, so now it should be at 4
expect(doubler.double).to.have.property('callCount', 4)
// let's confirm the 3rd call (index 2)
expect(doubler.double)
  .to.have.property('thirdCall')
  .to.have.property('args')
  .to.deep.equal(['third'])
// equivalent way of getting any call
expect(doubler.double.getCall(2))
  .to.have.property('args')
  .to.have.property(0)
  .to.equal('third')
// let's confirm the last call
expect(doubler.double)
  .to.have.property('lastCall')
  .to.have.property('args')
  .to.deep.equal([4])

Stub by specific argument value

You can returns different mocked responses based on the specific value of the arguments.

const doubler = {
  double(x) {
    return x + x
  },
}

cy.stub(doubler, 'double')
  .withArgs('first')
  .returns('11')
  .withArgs('second')
  .returns('22')

To add other stub behaviors take the stubbed method and use Sinon helpers.

expect(doubler.double('first')).to.equal('11')
expect(doubler.double('second')).to.equal('22')
expect(doubler.double('first')).to.equal('11')
expect(doubler.double).to.have.been.calledThrice

All other called get undefined

expect(doubler.double(4)).to.equal(undefined)

If you want to call the original method for all unmatched arguments, use callThrough()

Stub by type of the argument

const doubler = {
  double(x) {
    return x + x
  },
}

cy.stub(doubler, 'double')
  .withArgs(Cypress.sinon.match.number)
  .returns(42)
  .withArgs(Cypress.sinon.match.string)
  .returns('hello')

All other called get undefined

expect(doubler.double('first')).to.equal('hello')
expect(doubler.double(3)).to.equal(42)
expect(doubler.double(true)).to.equal(undefined)
expect(doubler.double).to.have.been.calledThrice

Stub by your own predicate

Unfortunately, seems in Cypress v12 the Sinon version has broken withArgs(sinon.match(predicate)) feature. Thus the following test should work, but does not

const doubler = {
  double(x) {
    return x + x
  },
}

Let's only stub even number arguments.

const isEven = (n) => n % 2 === 0

cy.stub(doubler, 'double')
  .withArgs(Cypress.sinon.match(isEven, 'even'))
  .returns(42)
  // all other calls go to the original method
  .callThrough()
expect(doubler.double('first')).to.equal('firstfirst') // original method
expect(doubler.double(3)).to.equal(42)

Note: while stubbing with the predicate match might be broken, the assertion calledWithMatch is working, see the recipe Stub called with the match.

Stub with a resolved promise

If the original method returns a promise (is asynchronous), you should use stub.resolves method

const doubler = {
  async double(x) {
    return x + x
  },
}

cy.stub(doubler, 'double')
  // give the stub an alias
  .as('double')
  // all calls should execute the original method
  .callThrough()
  // except the first call that should resolve with "foo"
  .onFirstCall()
  .resolves('foo')

Let's call the method and confirm the resolved value.

doubler.double(42).then((value) => {
  expect(value, 'the first result').to.equal('foo')
})

Let's confirm that the second call resolves with the actual doubled argument

doubler.double(8).then((value) => {
  expect(value, 'real result').to.equal(16)
})

The stub was called twice

cy.get('@double').should('have.been.calledTwice')