Testing React Function Components with Hooks using Enzyme
A React Function Component is simply a function that returns a React element. With React 16.8 the most awaited feature, hooks was introduced which allowed for injecting state and lifecycle methods into stateless function components and make it stateful. The simple syntax and plug and play ability of hooks made writing function components quite enjoyable and made writing class based components feel a bit cumbersome.
Consider this simple React function component implementing hooks:
The above example consists of a simple form as a function component with an uncontrolled email input field and a controlled password input field with internal methods that update the state using the useState and useEffect hooks and a submit button that dispatches a submit action.
By writing test specs for this component I’ll be covering the various use cases that we have to deal with while writing tests for React Function Components.
Enzyme and Jest
Shallow vs Mount
In layman terms, mount renders a component to its extreme leaf nodes whereas shallow as the name suggests does a shallow render i.e. renders just the component and not its children.
I prefer shallow render over mounting the component as it helps testing a component as a unit rather than asserting the behavior of components inside a unit component. This is useful as we use an UI component library like Reactstrap in our source code. So we wouldn’t want to test the components from this library (as it’s already been done in the library itself). If we used mount then even the component library we used would render to its leaf node. Shallow rendering helps us use the components without rendering them to their smallest html unit nodes. Also shallow rendering has performance benefits when compared with mount.
I used to write class based components and test by shallow rendering them using enzyme and jest. Testing class based components are well documented in the Enzyme documentation. As for testing function components the documentation was scarce as it had just been released when I first started implementing it. React recommends using react-testing-library to test hooks which is based on mount.
I could figure out no proper way to access and test internal methods that would update the state of the component using Enzyme. So after googling for hours and not finding a proper solution on how to write test specs for function component using shallow and enzyme I did what every developer should do i.e. I posted a question on stackoverflow. Then Alex replied:
It seemed to be the correct approach for testing the function component as we couldn’t know if the hooks were being called by spying or stubbing them but we could determine the state updates that the hooks carried out by looking at the updated props.
Testing component ui and props
So for testing the Login component we shallow render it. To check the integrity of the UI we test the snapshot of the UI. A snapshot is the textual snap of html content of the rendered component. This covers the presence of all the elements and would fail if anything were to change as the new snapshot would not match the previous one.
Then to test the rendered components we use the find selector method to make sure that the elements are present and match the props so as to detect the absence or change in props.
We could have tested individual prop instead of testing all the props.
For example, for testing the value prop of password field we could have used:
but we preferred checking all the props as it simplifies the task of writing tests. You wont need to decide on what props needs to be tested and no props are left out so it saves time and effort.
Now to test the login components with props passed we copy the same method as above and update the necessary props that would change when the initialProps are passed.
Testing state updates
States are maintained in function components using useState hooks. As the state hooks are internal to the component they aren’t exposed thus they can’t be tested by calling them. Thus to test if a state has updated we simulate events or call the method props of the component and check if the state has updated by determining the update to the props of the rendered components.
In other words we check for direct-effects where direct-effects of a simulated event represents the expected changes / side-effects in the props as a result of the event.
Support for useState has been added fairly recently from React 16.8.5 thus requires the same or above version of React.
An alternative to simulating events using simulate method is to execute the props by calling them as functions by passing in the necessary params.
It is useful when we have a custom component with custom methods as props.
Here to trigger onDropdownClose we do:
Lifecycle hooks such as useEffect aren’t yet supported in shallow render (those hooks don’t get called) so we need to use mount instead of shallow to test those components for now. Like with the useState hook we check for updates to props to test these hooks by simulating events or executing props as functions.
The support for these lifecycle hooks in enzyme are being tracked here.
Update: You can enable useEffect hooks while shallow mounting by using
jest-react-hooks-shallowpackage . I’ve written about how you can do it here:
Testing useEffect and Redux Hooks using Enzyme
Here’s how you can test effect and redux hooks while shallow mounting components using Enzyme and Jest.
Methods that don’t update state
The methods that don’t manipulate the state can be refactored out of the component into a separate utils file and tested in it instead of having them inside the component. If the methods are pretty specific to the component and aren’t shared outside the component we could have it inside the component file but outside the main function component. To standardize the methods we could abstract them into a single method.
Then testing them would be pretty straight forward.
Testing uncontrolled components
But what about testing uncontrolled components? Since email input field is uncontrolled, the email state isn’t reflected in the value of the component. If we set the value prop it throws an error saying that a component would also require an onChange method otherwise the component would be readonly as it would be a controlled component and wouldn’t allow us to enter anything into the input field.
Thus to make it testable without setting the value prop we set the data-attribute with the value of email state.
After setting the value to data-value prop we can easily test uncontrolled components like we did for controlled components by simulating events and checking direct-effects in the data-value prop.
And by now the test coverage should have reached 100% which means that you have successfully tested your component with proper coverage.
Refactor to stateless components and a custom hook (Optional)
To mitigate the issue regarding the uncontrolled components, a different implementation was suggested (thanks to Rohit dai) such that the state and lifecycle hooks are segregated out of the actual component that is to be tested into a custom hook.
The hooks are separated out into a function which returns an object of element props which are then injected into individual elements in the actual function component. With this implementation, function component is separated into a custom hook and a stateless function component. The stateless function component is made stateful by injecting our custom hook.
This would solve the problem of uncontrolled components as we could even export the value of email state using the value property (in emailField element) from our custom hook and still be able to disregard it in our main component by discarding props that aren’t required just like in the above example by only using required properties. We just used onBlur in the above example from the emailField element. We can now expose all the methods as properties in field props for testing without actually having to use all of them.
Testing our custom hook
So now for testing our custom hook we wrap it inside a function component, As if we don’t do so, the tests would fail as hooks are designed to run only inside a function component. Then we expect the functional aspect of our code to be present and working functionally inside our custom hook.
And in the main login component we just test the UI elements and not the functionality or behavior. Thus we would be able to segregate the UI from the Behavior of the app.
- Test entire props object of a rendered component instead of a single prop
- Reuse the spec to test component with and without props passed
- Check direct-effects for testing hooks by simulating events
- To test unsupported hooks use mount and check for direct-effects
- Refactor methods out of the component that don’t update component state
- Use data-attributes to test state of uncontrolled components
These aren’t the ways set in stone to test the components but something that I have been using. Hope this was helpful in some way. Please let me know in the comments if using custom hooks to segregate state from our function components is the proper way of refactoring for testing a function component or if you know any better ways to test React Function Components.
Thanks for reading.👏 😇