codemon
codemon

Reputation: 1594

How do I test a react hook method?

I am using jest/istanbul to track code coverage. I have the following component:

 // Menu.jsx    
  const myComponent = ({ initialState }) = {
        const [state, setState] = useState(initialState);
        const [menuContent, setMenuContent] = useState(undefined);

        return (
            <Menu>
                {state.accordions.map(item, index) => (
                    <MenuPanel id={item.id}>
                        {item.details && typeof item.details === 'function'
                            ? <item.details setContent={myContent => setMenuContent(myContent)} />
                            : undefined}
                    </MenuPanel>
                )}
            </Menu>
        )
    }

In my test for Menu.jsx the jest coverage report is complaining that setMenuContent is not covered in my tests. How am I supposed to test a hook like that? I thought it wasn't possible. I tried testing that the setContent prop exists on the subcomponent but that didn't help. Any ideas on how to get this to pass the coverage report? I am using shallow rendering in my test.

Upvotes: 0

Views: 643

Answers (1)

ZEE
ZEE

Reputation: 5849

You can mock useState for this specific component, try this:

const stateMock = jest.fn();

jest.mock('react', () => {
  const ActualReact = require.requireActual('react');
  return {
    ...ActualReact,
    useState: () => ['value', stateMock], // what you want to return when useContext get fired goes here
  };
});

Everytime your component calls useState your stateMock will be fired and your case will be covered

This is just a minimal example of what you can do, but you can enhance the mock to recognize each state call

If you want to keep the default behaviour of React state you can declare your callback function outside of the component body, in your case the concerned line of code is here:

<item.details setContent={myContent => setMenuContent(myContent)} />

So instead of this anonymous function which can lead to memory leak anyways, you can take it out of your component and do something like so:

   const setContent = (setMenuContent) => (myContent) => setMenuContent(myContent)


  const myComponent = ({ initialState }) = {
    const [state, setState] = useState(initialState);
    const [menuContent, setMenuContent] = useState(undefined);

    return (
        <Menu>
            {state.accordions.map(item, index) => (
                <MenuPanel id={item.id}>
                    {item.details && typeof item.details === 'function'
                        ? <item.details setContent={setContent(setMenuContent)} />
                        : undefined}
                </MenuPanel>
            )}
        </Menu>
    )
}

Now you can export this function and cover it with a text, which will allow you to mock setMenuContent

export const setContent = (setMenuContent) => (myContent) => setMenuContent(myContent)

Your last option is to use something like enzyme or react-testing-lib, find this item.details component in the dom and trigger a click action

Upvotes: 1

Related Questions