I created a custom react hook that is supposed to handle all less important api requests, which i don't want to store in the redux state. Hook works fine but I have trouble testing it. My test setup is jest and enzyme, but I decided to give a try react-hooks-testing-library here as well.
What I have tried so far is to first mock fetch request with a fetch-mock library, what works fine. Next, i render hook with renderHook
method, which comes from react-hooks-testing-library. Unfortunately, looks like I do not quite understand the waitForNextUpdate
method.
This is how my hook looks like.
useApi hook
export function useApi<R, B = undefined>(
path: string,
body: B | undefined = undefined,
method: HttpMethod = HttpMethod.GET
): ResponseStatus<R> {
const [response, setResponse] = useState();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | boolean>(false);
useEffect(() => {
const fetchData = async (): Promise<void> => {
setError(false);
setIsLoading(true);
try {
const result = await callApi(method, path, body);
setResponse(result);
} catch (errorResponse) {
setError(errorResponse);
}
setIsLoading(false);
};
fetchData();
}, [path, body, method]);
return { response, isLoading, error };
}
Hook can take 3 different state combinations that I would like to test. Unfortunately, I have no idea how.
Loading data:
{ response: undefined, isLoading: true, error: false }
Loaded data:
{ response: R, isLoading: false, error: false }
Error:
{ response: undefined, isLoading: false, error: true }
This is how my test looks like at this moment:
import fetchMock from 'fetch-mock';
import { useApi } from './hooks';
import { renderHook } from '@testing-library/react-hooks';
test('', async () => {
fetchMock.mock('*', {
returnedData: 'foo'
});
const { result, waitForNextUpdate } = renderHook(() => useApi('/data-owners'));
console.log(result.current);
await waitForNextUpdate();
console.log(result.current);
});
callApi func
/**
* Method to call api.
*
* @param {HttpMethod} method - Request type.
* @param {string} path - Restful endpoint route.
* @param {any} body - Request body data.
*/
export const callApi = async (method: HttpMethod, path: string, body: any = null) => {
// Sends api request
const response = await sendRequest(method, path, body);
// Checks response and parse to the object
const resJson = response ? await response.json() : '';
if (resJson.error) {
// If message contains error, it will throw new Error with code passed in status property (e.g. 401)
throw new Error(resJson.status);
} else {
// Else returns response
return resJson;
}
};
1st case: unit testing simple custom hooks First, we need to create a test using describe. Then, we render a hook to use it for testing. Common way of doing it is to use the renderHook method from the React Hooks Testing Library. The next step is to figure out which cases we need to check.
import { renderHook, act } from '@testing-library/react-hooks'; import useCounter from '../hooks/useCounter'; describe('useCounter', () => { test(`Default value of `count` will be zero`, () => { const { result } = renderHook(() => useCounter()); expect(result. current. count). toBe(0); }); });
If you need to test a custom Hook, you can do so by creating a component in your test, and using your Hook from it. Then you can test the component you wrote. To reduce the boilerplate, we recommend using React Testing Library which is designed to encourage writing tests that use your components as the end users do.
No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
It's all right that you did. Now, you need use expect for test.
const value = {...}
expect(result.current).toEqual(value)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With