Stub window.track

How to stub the event analytics method window.track.

The analytics method window.track

Imagine our app is loading a tiny analytics script that tracks the window load event.

analytics.js
1
2
3
4
5
6
7
// example analytics lib
window.track = (eventName) => {
console.log('tracking event "%s"', eventName)
}
window.addEventListener('load', () => {
track('window.load')
})

The application can also track other events, for example creating and removing the todo items

app.js
1
2
3
4
5
6
7
8
9
10
// adding a todo
track('todo.add', todo.title)
axios.post('/todos', todo).then(() => {
commit('ADD_TODO', todo)
})
// removing a todo
track('todo.remove', todo.title)
axios.delete(`/todos/${todo.id}`).then(() => {
commit('REMOVE_TODO', todo)
})

How do we test the analytics calls from the Cypress test?

Important: if you want the function to be stubbable, declare it as a property on the window object like window.track = () => {...} or window.track = function () {...}. Do NOT simply declare a global function like function track () {...}, since the global function would simply clash with our Object.defineProperty variable with the same name. See the issue #15694 for example.

Stub the method after the visit

You can see me explaining how to stub the window.track after the page visit to track todo.add and todo.remove events in the video below

The final test code is below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <reference types="cypress" />
import { enterTodo, resetData, removeTodo } from '../support/utils'

describe('Stubbing window.track', () => {
beforeEach(resetData)

it('tracks added and removed todos', () => {
cy.visit('/').then((win) => {
cy.stub(win, 'track').as('track')
})
enterTodo('write tests')
cy.get('@track')
.should('have.been.calledOnceWithExactly', 'todo.add', 'write tests')
.invoke('reset')

removeTodo('write tests')
cy.get('@track').should(
'have.been.calledOnceWithExactly',
'todo.remove',
'write tests'
)
})
})

Testing window.load call

If we want to test that the window.load call happens, we have to stub the method window.track before the application calls it. We can use the cy.visit onBeforeLoad callback for this. It executes when the window object has been created, but the application code has not executed yet.

You can watch the video below

And see the finished code below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <reference types="cypress" />
import { resetData } from '../support/utils'

describe('Stubbing window.track', () => {
beforeEach(resetData)

it('tracks page load', () => {
cy.visit('/', {
onBeforeLoad(win) {
Object.defineProperty(win, 'track', {
get() {
return cy.stub().as('track')
},
set() {}
})
}
})
cy.get('@track').should('have.been.calledOnceWithExactly', 'window.load')

cy.reload()
})
})

Stub every widow

If the browser navigates to a different page, or even simply reloads the current page, the window object is recreated, removing our stub. Thus we need to recreate the stub every time a new window object is created and before the application code is loaded. We can do this by using the cy.on event callback:

The finished code is below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it('tracks page load', () => {
const track = cy.stub().as('track')
cy.on('window:before:load', (win) => {
Object.defineProperty(win, 'track', {
get() {
return track
},
set() {}
})
})

cy.visit('/')
cy.get('@track').should('have.been.calledOnceWithExactly', 'window.load')
cy.reload()
cy.get('@track').should('have.been.calledTwice')
})

See also