Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a custom hook inside of a React component you want to test?

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?

like image 612
sschottler Avatar asked Jun 07 '19 15:06

sschottler


People also ask

How do you test a custom hook in React?

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.

How do you mock a custom hook Jest?

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').

How do you test React hooks in Jest?

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.

Can we call hooks from custom hooks in React?

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.


2 Answers

To mock your custom hook using jest.

import * as useCustomHook from '../hooks/useCustomHooks'

const spy = jest.spyOn(useCustomHook, 'default')
spy.mockReturnValue({
name: 'test'
})
like image 190
Kriti Avatar answered Oct 02 '22 06:10

Kriti


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.

like image 45
helloitsjoe Avatar answered Oct 02 '22 06:10

helloitsjoe