Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "mock" navigator.geolocation in a React Jest Test

I'm trying to write tests for a react component I've built that utilizes navigator.geolocation.getCurrentPosition() within a method like so (rough example of my component):

class App extends Component {

  constructor() {
    ...
  }

  method() {
    navigator.geolocation.getCurrentPosition((position) => {
       ...code...
    }
  }

  render() {
    return(...)
  }

}

I'm using create-react-app, which includes a test:

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
});

This test fails, printing out this in the console:

TypeError: Cannot read property 'getCurrentPosition' of undefined

I'm new to React, but have quite a bit of experience with angular 1.x. In angular it is common to mock out (within the tests in a beforeEach) functions, "services", and global object methods like navigator.geolocation.etc. I spent time researching this issue and this bit of code is the closest I could get to a mock:

global.navigator = {
  geolocation: {
    getCurrentPosition: jest.fn()
  }
}

I put this in my test file for App, but it had no effect.

How can I "mock" out this navigator method and get the test to pass?

EDIT: I looked into using a library called geolocation which supposedly wraps navigator.getCurrentPosition for use in a node environment. If I understand correctly, jest runs tests in a node environment and uses JSDOM to mock out things like window. I haven't been able to find much information on JSDOM's support of navigator. The above mentioned library did not work in my react app. Using the specific method getCurrentPosition would only return undefined even though the library itself was imported correctly and available within the context of the App class.

like image 335
timothym Avatar asked Mar 24 '17 20:03

timothym


6 Answers

It appears that there is already a global.navigator object and, like you, I wasn't able to reassign it.

I found that mocking the geolocation part and adding it to the existing global.navigator worked for me.

const mockGeolocation = {
  getCurrentPosition: jest.fn(),
  watchPosition: jest.fn()
};

global.navigator.geolocation = mockGeolocation;

I added this to a src/setupTests.js file as described here - https://create-react-app.dev/docs/running-tests#initializing-test-environment

like image 88
Joe Race Avatar answered Oct 04 '22 19:10

Joe Race


I know this issue might have been solved, but seems that all the solutions above are all wrong, at least for me.

When you do this mock: getCurrentPosition: jest.fn() it returns undefined, if you want to return something, this is the correct implementation:

const mockGeolocation = {
  getCurrentPosition: jest.fn()
    .mockImplementationOnce((success) => Promise.resolve(success({
      coords: {
        latitude: 51.1,
        longitude: 45.3
      }
    })))
};
global.navigator.geolocation = mockGeolocation;

I am using create-react-app

like image 20
Matteo Avatar answered Oct 04 '22 18:10

Matteo


A TypeScript version for anyone that was getting Cannot assign to 'geolocation' because it is a read-only property.

In the mockNavigatorGeolocation.ts file (this can live in a test-utils folder or similar)

export const mockNavigatorGeolocation = () => {
  const clearWatchMock = jest.fn();
  const getCurrentPositionMock = jest.fn();
  const watchPositionMock = jest.fn();

  const geolocation = {
    clearWatch: clearWatchMock,
    getCurrentPosition: getCurrentPositionMock,
    watchPosition: watchPositionMock,
  };

  Object.defineProperty(global.navigator, 'geolocation', {
    value: geolocation,
  });

  return { clearWatchMock, getCurrentPositionMock, watchPositionMock };
};

I then import this in my test at the top of the file:

import { mockNavigatorGeolocation } from '../../test-utils';

And then use the function like so:

const { getCurrentPositionMock } = mockNavigatorGeolocation();
getCurrentPositionMock.mockImplementation((success, rejected) =>
  rejected({
    code: '',
    message: '',
    PERMISSION_DENIED: '',
    POSITION_UNAVAILABLE: '',
    TIMEOUT: '',
  })
);
like image 38
Jamie Avatar answered Oct 04 '22 17:10

Jamie


Mocking with setupFiles

// __mocks__/setup.js

jest.mock('Geolocation', () => {
  return {
    getCurrentPosition: jest.fn(),
    watchPosition: jest.fn(),
  }
});

and then in your package.json

"jest": {
  "preset": "react-native",
  "setupFiles": [
    "./__mocks__/setup.js"
  ]
}
like image 40
chinloong Avatar answered Oct 04 '22 18:10

chinloong


I followed @madeo's comment above to mock global.navigator.geolocation. It worked!

Additionally I did the following to mock global.navigator.permissions:

  global.navigator.permissions = {
    query: jest
      .fn()
      .mockImplementationOnce(() => Promise.resolve({ state: 'granted' })),
  };

Set state to any of granted, denied, prompt as per requirement.

like image 38
sm7 Avatar answered Oct 04 '22 17:10

sm7


For whatever reason, I did not have the global.navigator object defined, so I had to specify it in my setupTests.js file

const mockGeolocation = {
  getCurrentPosition: jest.fn(),
  watchPosition: jest.fn(),
}
global.navigator = { geolocation: mockGeolocation }
like image 44
IonicBurger Avatar answered Oct 04 '22 19:10

IonicBurger