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

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')
  .callThrough()
  .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 calls after the first two execute the original doubler.double method

expect(doubler.double('third')).to.equal('thirdthird')
expect(doubler.double(4)).to.equal(8)

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.