How to return difference values depending on the stub's arguments.
Sinon.js is my favorite JavaScript library for spying and stubbing object methods. This blog post describes a few seldomly used stub features that I always have to look up. These examples come from my Cypress examples page.
callThrough
Imagine we want to stub a method, and return a value for the specific argument, but let the original method be called for all other arguments. We can use the callThrough feature.
const stub = cy.stub(greeter, 'greet') // all non-matched calls should call the real method stub.callThrough() // all calls with a string argument should get "Hi" stub.withArgs(Cypress.sinon.match.string).returns('Hi') // call the "greet" method expect(greeter.greet('World')).to.equal('Hi') expect(greeter.greet(42)).to.equal('Hello, 42!')
callsFake
To have the absolute power over the stub, I sometimes use the `callsFake Sinon feature to redirect the method calls to my own function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
const person = { getName() { return'Joe' }, }
cy.stub(person, 'getName').callsFake(() => { return ( // call the real person.getName() using the wrappedMethod person.getName .wrappedMethod() // but then reverse the returned string .split('') .reverse() .join('') ) }) expect(person.getName()).to.equal('eoJ')
It allows me to programmatically decide what to do with the call. In my repo cypress-form-opens-second-tab-example I am stubbing the document.createElement calls when the argument is form, but let the original method be called for all other elements.
1 2 3 4 5 6 7 8 9 10 11 12 13
const create = doc.createElement.bind(doc) cy.stub(doc, 'createElement').callsFake((name) => { if (name === 'form') { const form = create('form') cy.stub(form, 'target').value('_self') // Also spy on the instance method "submit" // so that later we can validate the submitted form cy.spy(form, 'submit').as('submit') return form } else { returncreate(name) } })
value
You can stub an object's property using the value keyword. For example, to prevent anyone from setting the target property in the above <form> example, I used the following syntax:
1
cy.stub(form, 'target').value('_self')
The application code can do the following:
1 2
const form = document.createElement('form') form.target = 'test_blank'
Yet, the form element will always submit in the current browser window, because the form.target will always remain _self.
resetHistory
Sinon spies/stubs keep the history of their calls. We can reset the history whenever we want
// test subject const person = { age: 0, birthday() { this.age += 1 }, } // spy on the subject's method cy.spy(person, 'birthday').as('birthday') cy.wrap(person) .its('age') .should('equal', 0) .then(() => { // the application calls the method twice person.birthday() person.birthday() }) // verify the spy recorded two calls cy.get('@birthday').should('have.been.calledTwice') cy.get('@birthday').its('callCount').should('equal', 2) // reset the spy's history cy.get('@birthday').invoke('resetHistory') // the spy call count and the history have been cleared cy.get('@birthday').its('callCount').should('equal', 0) cy.get('@birthday').should('not.have.been.called')
restore
When you no longer want to use the stub, call .restore() method on the stub
1 2 3 4 5 6 7 8 9 10 11 12
const person = { getName() { return'Joe' }, }
expect(person.getName(), 'true name').to.equal('Joe') cy.stub(person, 'getName').returns('Cliff') expect(person.getName(), 'mock name').to.equal('Cliff') // restore the original method person.getName.restore() expect(person.getName(), 'restored name').to.equal('Joe')
If use have a Cypress alias, you can also invoke the restore method: