Home > Software engineering >  Mocking GeoLocation API Hook In React
Mocking GeoLocation API Hook In React

Time:09-12

I currently have the following custom hook to access the geolocation API:

const useGeoLocAPI = () => {
  const [coords, setCoords] = useState(null);
  if ("geolocation" in navigator) {
    navigator.geolocation.getCurrentPosition(
      (position) => {
        setCoords({
          lat: position.coords.latitude,
          long: position.coords.longitude,
        });
      },
      (error) => {
        console.error(error.message);
      }
    );
  }
  return [coords, setCoords];
};

export default useGeoLocAPI;

Since the success callback uses setState , i'm having a hard time trying to mock this hook for testing. I've also heard mocking setState is an anti-pattern, which makes me wonder how I would even do this. I am able to do a simple smoke test by creating the following mock in my setupTests.js

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

global.navigator.geolocation = mockGeolocation;

But because this mock doesn't setState, the hook always returns null in my tests.

Is there a way to properly mock the geolocation API so I can do the following:

  • Assert the shape of the state.
  • Assert it's error handling
  • Assert that it can actually set state

I've created an issue of this in the project's repo in case more information is needed.

CodePudding user response:

The hook always returns null in the tests because you need to return a value in your mocked getCurrentPosition method.

You can return a value in the mocked method by implementing a function to return the value you want.

i.e.,

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

    const { result } = renderHook(() => useGeoLocAPI());
    const [ coords ] = result.current;

    expect(coords)
        .toEqual({
            lat: 51.1,
            long: 45.3
        });
});

You can also mock the geolocation getCurrentPosition method to return an error in order to test your error logic.

test('useGeoLocAPI error handling', () => {
    const mockGeolocation = {
        getCurrentPosition: jest.fn()
            .mockImplementationOnce((success, error) => Promise.resolve(error({ message: 'nav error' })))
    };
    global.navigator.geolocation = mockGeolocation;
    const consoleErrorMock = jest.spyOn(global.console, 'error').mockImplementation();

    const { result } = renderHook(() => useGeoLocAPI());
    const [ coords ] = result.current;

    expect(global.console.error)
        .toHaveBeenCalledWith('nav error');
    expect(coords)
        .toBeNull();
    consoleErrorMock.mockRestore();
});

Now that you know how to mock the geolocation getCurrentPosition method properly, you should be able to test the setState functionality of your hook.

  • Related