Stub calls depending on the arguments
Cypress bundled Sinon.js 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')