Skip to content
This repository has been archived by the owner on Feb 28, 2023. It is now read-only.

Commit

Permalink
more stubbing examples (#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov authored Mar 28, 2021
1 parent b75bef6 commit 981d0c8
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 48 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ See the presentation at [https://testing-workshop-cypress.netlify.app/][presenta
| [🔗](#component-testing) | Component testing | [17-component-testing](cypress/integration/17-component-testing) | [17-component-testing](slides/17-component-testing/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=17-component-testing)
| [🔗](#backend) | Backend code | [18-backend](cypress/integration/18-backend) | [18-backend](slides/18-backend/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=18-backend)
| [🔗](#code-coverage) | Code coverage | [19-code-coverage](cypress/integration/19-code-coverage) | [19-code-coverage](slides/19-code-coverage/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=19-code-coverage)
| | Stubbing methods | [20-stubbing](./cypress/integration/20-stubbing) | [20-stubbing](./slides/20-stubbing/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=20-stubbing)
| [🔗](#stubbing-methods) | Stubbing methods | [20-stubbing](./cypress/integration/20-stubbing) | [20-stubbing](./slides/20-stubbing/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=20-stubbing)
| | The end | - | [end](slides/end/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=end)

## For speakers 🎙
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/12-custom-commands/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ it.skip('passes when object gets new property', () => {
// add assertions
})

it.only('creates todos', () => {
it('creates todos', () => {
cy.get('.new-todo')
.type('todo 0{enter}')
.type('todo 1{enter}')
Expand Down
50 changes: 50 additions & 0 deletions cypress/integration/20-stubbing/answer-cy-on.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference types="cypress" />

describe('cy.on', () => {
// these test handles every "window" object
// by attaching the stub whenever any window is created
// https://on.cypress.io/catalog-of-events
it('works', () => {
// there is no "window.track" yet,
// thus we cannot stub just yet
let track // the real track when set by the app
let trackStub // our stub around the real track

// use "cy.on" to prepare for "window.track" assignment
// this code runs for every window creation, thus we
// can track events from the "cy.reload()"
cy.on('window:before:load', (win) => {
Object.defineProperty(win, 'track', {
get() {
return trackStub
},
set(fn) {
// if the stub does not exist yet, create it
if (!track) {
track = fn
// give the created stub an alias so we can retrieve it later
trackStub = cy.stub().callsFake(track).as('track')
}
}
})
})

cy.visit('/')

// make sure the page called the "window.track" with expected arguments
cy.get('@track').should('have.been.calledOnceWith', 'window.load')

cy.reload()
cy.reload()

cy.get('@track')
.should('have.been.calledThrice')
// confirm every call was with "window.load" argument
.invoke('getCalls')
.should((calls) => {
calls.forEach((trackCall, k) => {
expect(trackCall.args, `call ${k + 1}`).to.deep.equal(['window.load'])
})
})
})
})
65 changes: 65 additions & 0 deletions cypress/integration/20-stubbing/answer-cypress-on.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/// <reference types="cypress" />

// variable that will hold cy.stub created in the test
let stub

// use Cypress.on in its own spec to avoid every test running it
// https://on.cypress.io/catalog-of-events
Cypress.on('window:before:load', (win) => {
// if the test has prepared a stub
if (stub) {
// the stub function is ready
// always returns it when the application
// is trying to use "window.track"
Object.defineProperty(win, 'track', {
get() {
return stub
}
})
}
})

describe('Cypress.on', () => {
beforeEach(() => {
// let the test create the stub if it needs it
stub = null
})

it('works', () => {
// note: cy.stub returns a function
stub = cy.stub().as('track')
cy.visit('/')

// make sure the page called the "window.track" with expected arguments
cy.get('@track').should('have.been.calledOnceWith', 'window.load')

cy.reload()
cy.reload()

cy.get('@track')
.should('have.been.calledThrice')
// confirm every call was with "window.load" argument
.invoke('getCalls')
.should((calls) => {
calls.forEach((trackCall, k) => {
expect(trackCall.args, `call ${k + 1}`).to.deep.equal(['window.load'])
})
})
})

it('works with reset', () => {
stub = cy.stub().as('track')
cy.visit('/')

// make sure the page called the "window.track" with expected arguments
cy.get('@track')
.should('have.been.calledOnceWith', 'window.load')
// cy.stub().reset() brings the counts back to 0
.invoke('reset')

cy.reload()
cy.reload()

cy.get('@track').should('have.been.calledTwice')
})
})
69 changes: 24 additions & 45 deletions cypress/integration/20-stubbing/answer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ describe('Stubbing window.track', () => {
cy.get('@track').should('have.been.calledWith', 'todo.remove', 'write code')
})

it('resets the count', () => {
cy.visit('/').then((win) => {
cy.stub(win, 'track').as('track')
})

enterTodo('write code')
cy.get('@track').should('be.calledOnce')

enterTodo('write tests')
cy.get('@track')
.should('be.calledTwice')
// reset the stub
.invoke('reset')

cy.get('@track').should('not.be.called')
enterTodo('control the state')
cy.get('@track').should('be.calledOnce')
})

it('stops working if window changes', () => {
cy.visit('/').then((win) => {
cy.stub(win, 'track').as('track')
Expand All @@ -36,12 +55,16 @@ describe('Stubbing window.track', () => {

cy.reload()
enterTodo('write tests')

/* eslint-disable-next-line cypress/no-unnecessary-waiting */
cy.wait(500) // wait just in case the call happens late

// note that our stub was still called once
// meaning the second todo was never counted
cy.get('@track').should('be.calledOnce')
})

it.only('adds stub after reload', () => {
it('adds stub after reload', () => {
const trackStub = cy.stub().as('track')

cy.visit('/').then((win) => {
Expand Down Expand Up @@ -84,48 +107,4 @@ describe('Stubbing window.track', () => {
// make sure the page called the "window.track" with expected arguments
cy.get('@track').should('have.been.calledOnceWith', 'window.load')
})

it('works via event handler', () => {
// there is no "window.track" yet,
// thus we cannot stub just yet
let track // the real track when set by the app
let trackStub // our stub around the real track

// use "cy.on" to prepare for "window.track" assignment
// this code runs for every window creation, thus we
// can track events from the "cy.reload()"
cy.on('window:before:load', (win) => {
Object.defineProperty(win, 'track', {
get() {
return trackStub
},
set(fn) {
// if the stub does not exist yet, create it
if (!track) {
track = fn
// give the created stub an alias so we can retrieve it later
trackStub = cy.stub().callsFake(track).as('track')
}
}
})
})

cy.visit('/')

// make sure the page called the "window.track" with expected arguments
cy.get('@track').should('have.been.calledOnceWith', 'window.load')

cy.reload()
cy.reload()

cy.get('@track')
.should('have.been.calledThrice')
// confirm every call was with "window.load" argument
.invoke('getCalls')
.should((calls) => {
calls.forEach((trackCall, k) => {
expect(trackCall.args, `call ${k + 1}`).to.deep.equal(['window.load'])
})
})
})
})
19 changes: 18 additions & 1 deletion cypress/integration/20-stubbing/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ describe('Stubbing window.track', () => {
// with expected arguments
})

it('resets the count', () => {
// add a couple of items
// confirm the stub was called N times
// reset the stub
// by invoking ".reset()" method
// trigger more events
// confirm the new number
})

it('adds stub after reload', () => {
// create a single stub with
// const trackStub = cy.stub().as('track')
Expand All @@ -44,12 +53,20 @@ describe('Stubbing window.track', () => {
// after the visit command confirm the stub was called
})

it('works via event handler', () => {
it('works via cy.on event handler', () => {
// need to return the same stub when using cy.visit
// and cy.reload calls that create new "window" objects
// tip: use the cy.on('window:before:load', ...) event listener
// which is called during cy.visit and during cy.reload
// during the test reload the page several times, then check
// the right number of "window.track" calls was made
// https://on.cypress.io/catalog-of-events
})

it('works via Cypress.on event handler', () => {
// create a single stub in the test
// return it to anyone trying to use window.track
// from Cypress.on('window:before:load') callback
// https://on.cypress.io/catalog-of-events
})
})
23 changes: 23 additions & 0 deletions slides/20-stubbing/PITCHME.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ it('tracks item delete', () => {
})
```

+++
## TODO: reset the count

```js
it('resets the count', () => {
cy.visit('/').then((win) => {
cy.stub(win, 'track').as('track')
})

enterTodo('write code')
cy.get('@track').should('be.calledOnce')

enterTodo('write tests')
cy.get('@track')
.should('be.calledTwice')
// reset the stub?

cy.get('@track').should('not.be.called')
enterTodo('control the state')
cy.get('@track').should('be.calledOnce')
})
```

---
## What if object changes

Expand Down

0 comments on commit 981d0c8

Please sign in to comment.