If you have a React component that calls a custom hook that fetches data, what is the best way to mock that internal custom hook result when testing the React component? I see 2 main approaches:
1) Jest.mock the custom hook. This seems to be the most recommended approach, but it seems like it requires the test to have more knowledge of internal implementation details and what it might need to mock than what the props interface of the component might suggest (assuming use of prop-types or TypeScript)
2) Use a dependency injection approach. Declare the hook as a prop, but default it to the real hook so you don't have to set it everywhere you render the component, but allow overriding with a mock for tests. Here is a contrived codesandbox example with a test that mocks a custom hook:
https://codesandbox.io/s/dependency-inject-custom-hook-for-testing-mjqlf?fontsize=14&module=%2Fsrc%2FApp.js
2 requires more typing, but seems easier to work with for testing. However, tests already have to have knowledge of internal implementation details of component to test any conditional logic for rendered output, so maybe that's not important and 1 is the best approach. Is 1 the way to go? What tradeoffs do you see? Am I missing another approach altogether?
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.
to call jest. mock with the module name and the function to mock the useClientRect hook with a function that returns the mocked values of the hook. import * as hooks from 'module_name'; it('a test', () => { jest. spyOn(hooks, 'useClientRect').
test. js file in the src folder and add the following: import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import useFetchData from './useFetchData. js'; import axios from 'axios'; jest.
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.
To mock your custom hook using jest.
import * as useCustomHook from '../hooks/useCustomHooks'
const spy = jest.spyOn(useCustomHook, 'default')
spy.mockReturnValue({
name: 'test'
})
This question is a few months old, but if you haven't found a good solution, I wrote a package that might help. I went through a similar thought process, including "what if I inject the hooks into the component?" Things got weird.
I basically wanted a connecter to avoid an extra wrapper for presentational components just to test them.
I came up with react-hooks-compose
, which lets you keep your hooks and your presenters separate, and test them individually or together: https://www.npmjs.com/package/react-hooks-compose
export const useFetch = () => {
const [user, setUser] = useState();
useEffect(() => {
fetchData('some-url') // <-- Fetches data on mount
.then(res => setUser(res.data));
}, []);
return {user};
}
// composeHooks passes the values from your hooks as props
export const UserPresenter = ({user}) => {
return <div>You fetched data for: {user.name}</div>;
}
export default composeHooks({ useFetch })(DataPresenter);
Now you don't have to mock the hook, you can just test the presenter with a prop:
it('presents user', () => {
const { queryByText } = render(<UserPresenter user={{name: 'Mary'}} />); // <-- Named export
expect(queryByText('Mary')).toBeTruthy();
});
Or, you have the option of a higher-level integration test:
it('fetches data', () => {
fetchData.mockResolvedValue('Mary');
const { queryByText } = render(<UserWithData />); // <-- Default export
expect(queryByText('Mary')).toBeFalsy();
return wait(() => {
expect(queryByText('Mary')).toBeTruthy();
});
});
You can even unit test the hook if you like.
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