Cypress - stub watchPosition response several times

59 views Asked by At

I have a small app in VueJs. When you click on a button, it launches the watchPosition function of the Geolocation API, get the user's position and do some calculations with the user's positions.

I would like to test this behaviour in Cypress test.

I've stubbed the watchLocation function like this, in a Cypress command:

Cypress.Commands.add('mockGeolocation', (latitude, longitude) => {
    cy.window().then((win) => {
      const mockGeolocation = {
        watchPosition: (successCallback, errorCallback, options) => {
          // Call the successCallback with mock position data
          const mockPosition = {
            coords: {
              latitude: latitude,
              longitude: longitude
            }
          }
          successCallback(mockPosition);
  
          // Optionally, you can call the errorCallback or handle options as needed
        },
        clearWatch: () => {
          console.log("hello there")
        }
      };
  
      // Replace the real geolocation object with the stub
      cy.stub(win.navigator, 'geolocation').value(mockGeolocation);
    });
  });

And then I call it in my test:

  it('My awesome test', () => {
   
    cy.visit('/')

    cy.mockGeolocation(1, 2)
    cy.get('[data-testid="start-button-game"]').click()
  })

It works but it works once. I would like to call cy.mockGeolocation again with different latitude and longitude to check if my app reacts as expected to a brand new position. And repeat the process several times in the same test.

How can I achieve that ?

1

There are 1 answers

6
Aladin Spaz On

The reason is, watchPosition is a watcher that is usually set once and responds to events from the navigator object in a push pattern.

The mock you have set up is for a function where data is fetched each time in a pull pattern.

I feel there must be a way to mock a watcher/listener, but it escapes me at the moment.

Instead, I recommend switching watchPosition to getCurrentPosition which seems more appropriate for something initiated by a button click.

Cypress.Commands.add('mockGeolocation', (latitude, longitude) => {
  cy.window().then((win) => {
    const mockGeolocation = {
      getCurrentPosition: (successCallback, errorCallback, options) => {
        ...  // remainder as already given


it('tests geolocation with mock', () => {

  cy.visit('/')

  cy.mockGeolocation(1, 2)
  cy.get('[data-testid="start-button-game"]').click()
  cy.get('location')
    .should('have.text', 'Your current position is Lat: 1 Long: 2')  // passes
  
  cy.mockGeolocation(3, 4)
  cy.get('[data-testid="start-button-game"]').click()
  cy.get('location')
    .should('have.text', 'Your current position is Lat: 3 Long: 4')  // passes
})

Tested with this app

<button data-testid="start-button-game" onclick="handler()">Get geolocaltion</button>
<location>Unknown</location>

<script>
  function success(pos) {
    const crd = pos.coords;
    const {latitude,longitude} = crd;
    const location = document.querySelector('location')
    location.innerText = `Your current position is Lat: ${latitude} Long: ${longitude}`
  }
  function error() {
  }
  const options = {}
  function handler() {
    window.navigator.geolocation.getCurrentPosition(success, error, options)
  }
</script>