Stub Geolocation

Imagine we have an application that tries to fetch the user's geographic location via Geolocation API getCurrentPositionopen in new window method. We want to confirm the application handles the error case correctly.

Find this recipe shown in the video Cypress Stub For Geolocation getCurrentPosition Method Using Sinon.jsopen in new window.

Sinon.js callsFake

<div id="message" />
<button id="locate">Locate me</button>
<script>
  document
    .getElementById('locate')
    .addEventListener('click', () => {
      navigator.geolocation.getCurrentPosition(
        function onSuccess() {
          // success getting the browser location
        },
        function onError(err) {
          const message = document.getElementById('message')
          message.innerText = err.message
        },
      )
    })
</script>
// simulate the browser API calling
// the "onError" callback function
// passed by the app to geolocation.getCurrentPosition
const error = new Error('Test geo error')
cy.window().then((win) => {
  cy.stub(win.navigator.geolocation, 'getCurrentPosition')
    .callsFake((onSuccess, onError) => {
      // our stub just calls the "onError" argument
      onError(error)
    })
    .as('getCurrentPosition')
})
cy.get('#locate').click()
// confirm the application behaves as it should
cy.contains('#message', error.message)
// and the stub was actually used
cy.get('@getCurrentPosition').should('have.been.calledOnce')

Sinon.js callsArgWith

Stubbing a function and calling an argument passed by caller is a common operation, thus Sinon.js has a built-in mechanism for it via callsArg and callsArgWith methods.

// instead of implementing a function "callsFake"
cy.stub(
  win.navigator.geolocation,
  'getCurrentPosition',
).callsFake((onSuccess, onError) => {
  // our stub just calls the "onError" argument
  onError(error)
})
// we can simply say "stub calls the argument at index 1"
// with the given argument "error"
cy.stub(
  win.navigator.geolocation,
  'getCurrentPosition',
).callsArgWith(1, error)
<div id="message" />
<button id="locate">Locate me</button>
<script>
  document
    .getElementById('locate')
    .addEventListener('click', () => {
      navigator.geolocation.getCurrentPosition(
        function onSuccess() {
          // success getting the browser location
        },
        function onError(err) {
          const message = document.getElementById('message')
          message.innerText = err.message
        },
      )
    })
</script>
// simulate the browser API calling
// the "onError" callback function
// passed by the app to geolocation.getCurrentPosition
const error = new Error('Test geo error')
cy.window().then((win) => {
  cy.stub(win.navigator.geolocation, 'getCurrentPosition')
    .callsArgWith(1, error)
    .as('getCurrentPosition')
})
cy.get('#locate').click()
// confirm the application behaves as it should
cy.contains('#message', error.message)
// and the stub was actually used
cy.get('@getCurrentPosition').should('have.been.calledOnce')

See also