Prepare To Spy

📺 Watch these examples explained in the video Prepare To Spy On A Method Added Dynamicallyopen in new window.

Wait for the property to exist

We can automatically wait for a new window property to be added before spying on it. For example, let's spy on the window.logEvent method after the application sets it up.

<button id="action">Action!</button>
<script>
  setTimeout(() => {
    console.log('loading app')
    window.logEvent = (name) => console.log('log event %s', name)
    // immediately log something
    logEvent('initialized')
  }, 10)
  document
    .getElementById('action')
    .addEventListener('click', () => {
      logEvent('action')
    })
</script>
console.log('loading test')
cy.window().its('logEvent')
cy.window().then((win) => {
  cy.spy(win, 'logEvent').as('logEvent')
})

Now we can click on the button and confirm the spy logEvent is called correctly.

cy.contains('button', 'Action').click()
cy.get('@logEvent').should(
  'have.been.calledOnceWithExactly',
  'action',
)

Prepare to spy

But what about the very first logEvent call? The once that happens immediately after the application creates it?

window.logEvent = (name) => console.log('log event %s', name)
// immediately log something
logEvent('initialized')

How do we spy on the logEvent('initialized') call? By the time the test "figures" out the new property exists, it is too late.

cy.window().its('logEvent')
// too late, the app already called it
<button id="action">Action!</button>
<script>
  setTimeout(() => {
    console.log('loading app')
    window.logEvent = (name) => console.log('log event %s', name)
    // immediately log something
    logEvent('initialized')
  }, 10)
  document
    .getElementById('action')
    .addEventListener('click', () => {
      logEvent('action')
    })
</script>
console.log('loading test')
// prepare new stub instance
const stub = cy.stub().as('logEvent')
// prepare for the application to set "window.logEvent"
cy.window().then((win) => {
  let logEvent
  Object.defineProperty(win, 'logEvent', {
    get() {
      return logEvent
    },
    set(method) {
      // we want to spy on the "window.logEvent",
      // so we call the real method function
      logEvent = stub.callsFake(method)
    },
  })
})

As soon as the application tries to set the window.logEvent = ... we "catch" the assignment and wrap it in our Sinon stub.

cy.get('@logEvent').should(
  'have.been.calledOnceWithExactly',
  'initialized',
)