jest - react - mocked geolocation inside useEffect in hook - reactjs

how would you test a hook that is using navigator.geolocation.getCurrentPosition() method (mocked it already based on this: https://newdevzone.com/posts/how-to-mock-navigatorgeolocation-in-a-react-jest-test) in useEffect?
I can't see the way how to wait for result as upon calling the function it does not return anything and returned data are processed in passed callback. found some hackish solution that works when run debug using jest runner vsc extension, but does not for regular test run.
the hook:
const useGeolocation = () => {
const [coords, setCoords] = useState<GeolocationCoordinates>();
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setCoords(position.coords);
});
}, []);
return coords;
};
test:
import { renderHook } from '#testing-library/react';
it('should return non-empty coords', async () => {
const { result } = renderHook(() => useGeolocation());
// MISSING CODE
expect(result.current).not.toBe(undefined);
});
I'll be happy for any idea leading to solution so I don't need to give up on unit testing once again :slight_smile:

Related

How to mock useContext and useState in react for testing in jest?

im writing jest file for a feature, and while coming from Angualr jasmine seems very similar to jest.
Im encounter some issue while trying to mock some function in the jest file.
const MMycustomContext= React.createContext({});
export const userActionMenuPopUpBuilder = (action, target, id) => {
const { statePath,breadcrumbers, } = useContext(MMycustomContext);
const [showPopUp, setShowPopUp] = useState(false);
const [itemSelected, setItemSelected] = useState({});
}
And this is my Jest file:
describe('userActionMenuPopUpBuilder ', () => {
it('Should be Truthy', () => {
jest.spyOn(React, 'useContext').mockImplementationOnce(() => {
return {
name: 'this is a mock context return value'
}
})
const temp = userActionMenuPopUpBuilder (mockAction, target, id);
console.log(temp);
expect(temp).toBeTruthy();
});
});
when im running the Jest file, i get exception because useContext is undefined- calling
userActionMenuPopUpBuilder function from the jest file, i tried to spyOn and make a mock from useContext like i did above, but it dosent seems to work and i still gets undefined,
Any suggestions?
thanks

Make sure function from React.useContext is called in Jest

Given a custom hook that looks like this:
const getSavedInfo (id) => {
const endpoint = getEndpoint(id)
const {updateInfo} = React.useContext(infoContext)
axios.get(endpoint).then((res) => {
if (res) {
updateInfo(res.data)
}
})
}
How would I go about properly mocking the updateInfo method so I can make sure it was called within my jest test?
You need to pass the provider in test while rendering the custom hook. You can write your own provider function or use react-hooks-testing-library which does the same thing for you. Like this:
import { renderHook } from 'react-hooks-testing-library';
const wrapper = ({ children }) => (
<InfoContext.Provider value={{updateInfo: jest.fn}}>{children}</InfoContext.Provider>
);
it('should call update info', () => {
const { result } = renderHook(() => getSavedInfo(), { wrapper });
// rest of the test code goes here
});
You can find detailed explanation here

React useEffect hook jest unit test doesn't confirm function prop is called

I'm trying to write a unit test to check that a function (passed as a prop) gets called if another prop is true in useEffect hook. The unit test fails to confirm that the (mocked) function is called in the useEffect hook, but it can confirm that a function that is spyOn from an imported module is called. Does anyone know what might be the issue? Thanks!
import {getUser} from './Auth';
export function ComponentA({
shouldRetryExport,
someReduxDispatchFunc,
}) {
const handleExport = useCallback(async () => {
const user = await getUser();
someReduxDispatchFunc();
}, []);
useEffect(() => {
if (shouldRetryExport) {
handleExport();
}
}, [shouldRetryExport]);
return (<SomeComponent />)
});
Unit test:
import * as Auth from './Auth';
it('should call someReduxDispatchFunc if getUserAuthorization is true', () => {
const getAuthUserSpy = jest.spyOn(Auth, 'getUser');
const someReduxDispatchFuncMock = jest.fn();
const props = {
someReduxDispatchFunc: someReduxDispatchFuncMock,
shouldRetryExportWithUserReAuthorization: true,
};
enzyme.mount(<ComponentA {...props} />);
expect(getAuthUserSpy).toHaveBeenCalled(); // works -> returns true
expect(someReduxDispatchFuncMock).toHaveBeenCalled(); // doesn't work -> returns false
});
It seems like it has something to do with the useCallback with useEffect. If I remove the useCallback and add the logic within to useEffect, it can capture someReduxDispatchFuncMock has been called.
I don't think the problem is from either useCallback or useEffect. The problem is most likely your callback takes an async function which means it needs time to get resolved.
In order to this, you have to make your test as async then wait it to get resolved as following:
it('should call someReduxDispatchFunc if getUserAuthorization is true', async () => {
const getAuthUserSpy = jest.spyOn(Auth, 'getUser');
const someReduxDispatchFuncMock = jest.fn();
const props = {
someReduxDispatchFunc: someReduxDispatchFuncMock,
shouldRetryExport: true,
};
enzyme.mount(<ComponentA {...props} />);
// wait for getting resolved
await Promise.resolve();
expect(getAuthUserSpy).toHaveBeenCalled();
expect(someReduxDispatchFuncMock).toHaveBeenCalled();
});

How to test(assert) intermediate states of a React Component which uses hooks

This question is regarding: how to test a component which uses the useEffect hook and the useState hook, using the react-testing-library.
I have the following component:
function MyComponent() {
const [state, setState] = useState(0);
useEffect(() => {
// 'request' is an async call which takes ~2seconds to complete
request().then(() => {
setState(1);
});
}, [state]);
return <div>{state}</div>
}
When I render this application, the behavior I see is as follows:
The app initially renders 0
After ~2seconds, the app renders 1
Now, I want to test and assert this behavior using the react-testing-library and jest.
This is what I have so far:
import {render, act} from '#testing-library/react';
// Ignoring the describe wrapper for the simplicity
test('MyComponent', async () => {
let result;
await act(async () => {
result = render(<MyComponent />);
});
expect(result.container.firstChild.textContent).toBe(1);
})
The test passes. However, I also want to assert the fact that the user initially sees the app rendering 0 (before it renders 1 after 2 seconds).
How do I do that?
Thanks in advance.
As pointed out by Sunil Pai in this blog: https://github.com/threepointone/react-act-examples/blob/master/sync.md
Here's how I managed to solve this:
import {request} from '../request';
jest.mock('../request');
test('MyComponent', async () => {
let resolve;
request.mockImplementation(() => new Promise(resolve => {
// Save the resolver to a local variable
// so that we can trigger the resolve action later
resolve = _resolve;
}));
let result;
await act(async () => {
result = render(<MyComponent />);
});
// Unlike the non-mocked example in the question, we see '0' as the result
// This is because the value is not resolved yet
expect(result.container.firstChild.textContent).toBe('0');
// Now the setState will be called inside the useEffect hook
await act(async () => resolve());
// So now, the rendered value will be 1
expect(result.container.firstChild.textContent).toBe('1');
})

How to mock or assert whether window.alert has fired in React & Jest with typescript?

I am using jest tests to test my React project written in #typescript created with Create React App. I'm using react-testing-library. I have a form component which shows an alert if the form is submitted empty. I wanted to test this feature (maybe) by spying/mocking the window.alert but it doesn't work.
I tried using jest.fn() as suggested in many SO answers but that's not working too.
window.alert = jest.fn();
expect(window.alert).toHaveBeenCalledTimes(1);
Here's how I implemented it: Form.tsx
async handleSubmit(event: React.FormEvent<HTMLFormElement>) {
// check for errors
if (errors) {
window.alert('Some Error occurred');
return;
}
}
Here's how I built the React+Jest+react-testing-library test: Form.test.tsx
it('alerts on submit click', async () => {
const alertMock = jest.spyOn(window,'alert');
const { getByText, getByTestId } = render(<Form />)
fireEvent.click(getByText('Submit'))
expect(alertMock).toHaveBeenCalledTimes(1)
})
I think you might need to tweak your test ever so slightly by adding .mockImplementation() to your spyOn like so:
it('alerts on submit click', async () => {
const alertMock = jest.spyOn(window,'alert').mockImplementation();
const { getByText, getByTestId } = render(<Form />)
fireEvent.click(getByText('Submit'))
expect(alertMock).toHaveBeenCalledTimes(1)
})
You could try to use global instead of window:
global.alert = jest.fn();
expect(global.alert).toHaveBeenCalledTimes(1);
Alternatively, try to Object.assign
const alert = jest.fn()
Object.defineProperty(window, 'alert', alert);

Resources