reactjsapitestingweather

Mocking GeoLocation API Hook In React


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:

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


Solution

  • 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.