ReactJS application development leads to code base growth. Once a certain point is reached, an application becomes more complex. Tasks start to get more and more time consuming. Adding a new feature or even fixing a bug creates a real risk for your application. If there is no testing at this point, it should be introduced immediately. Testing makes an app more robust and less prone to error. It’s the best way to ensure that your code works as expected in every possible scenario.
There is nothing more frustrating than spotting an error that could have been found and removed during development, if only tests had been written. Everyone wants to create bug-free applications that users will love. To achieve this goal, every front-end developer has to ensure that at least crucial parts of their application are tested and the risk of introducing badly working code is minimal.
My goal is to share some practical knowledge about testing React applications using Jest and Enzyme.
This article contains code fragments from the following repository: https://github.com/dybik08/github-browser.
Modern React applications are made by stateless functional components with hooks. It is the direction React is evolving towards. In this article I won’t focus on testing class-based components which are displaced by functional ones.
We’ll learn how to test them starting with UI testing. After that we’ll move to React Hooks testing, then we’ll move to Redux Thunk asynchronous actions, and we’ll finish with Redux.
For the purpose of this article, I’ve made a small application which displays searched GitHub repository info. It uses Github v3 public API. For more info check the Github API docs here. Project structure can be found in the image below. The project was bootstrapped by React CLI which handled installation of all libraries and created a boilerplate project with a bunch of useful commands ready to use like npm run start or npm run test.
Getting back to the project, users can search Github repositories by filling in the input and pressing the search button or enter key on the keyboard. Users can then press the details button to see detailed info of the selected repository or check other repositories of the selected repository owner.
Enzyme render methods — what do they do and what are they for?
Mount — Ideal for use cases when you have components that may interact with the DOM API or use React lifecycle methods in order to fully test the component. This is the default method when writing component tests.
Shallow — Renders only a single component, not including its children. It is useful to isolate the component for pure unit testing. The biggest advantage is that it protects against changes or bugs in a child component.
A summary of both methods can be found in the table below.
Testing stateful functional components — useState hook
Mocking the useState hook
To properly test stateful functional components, it would be good to start with mocking the useState hook along with the setState function. The SearchReposInput component’s purpose is to handle text entered by the user and pass it to the Redux action on button press.
The image below shows how to handle mocking. The Jest.fn() method in the setState function will be needed to ensure setState was called with the correct arguments or the correct number of times. There is an option to set the default useState value, but in this case it will be left untouched.
Once the mock is correctly set up, it is time to write some tests. For this component, testing should start by finding an input. It can be done by CSS class name. It’s also a good practice to find input or any other interactive element by value seen by the user like placeholder text, button name, etc. An example of the test can be found in the image below.
Mocking Redux hooks
The code below, taken from SearchReposInput.test, also shows how to mock the React-Redux useSelector and useDispatch hooks. When writing tests for components using the Redux dispatch method to dispatch actions, there should be a spy assigned to the method.
It can be done the same way as you see in the image below. It will provide an option to check which function is dispatched along with its arguments. Tests that run on Redux’s store values need to return a mock value from the useSelector method. It provides better control over the tested component.
The code below, also taken from SearchReposInput.test, shows how to mock functions from external modules. It can be useful when a test needs specific conditions like a mocking response from a function calling an external API. In this example, along with Jest’s spy on a mocked function, it was necessary to mock function output. Mocked values should be close to the original response to make the testing case more realistic. This was achieved by using mockReturnedValue. When the expected response is a promise, the mockResolvedValue method should be used instead.
Testing logic inside the useEffect hook
One of the most common use cases for the useEffect hook is to execute API calls after component mounts. A good testing example of this feature is fetching data and further data processing in useEffect.
Once the user presses the details button on one of the repositories in the repositories list, a modal opens, displaying the selected repository details. One of the sections displays other repositories linked to the selected repository owner. Those “extra” repositories are fetched after the modal mounts, to be ready when users press the additional user repositories dropdown panel. To test if components behave as expected, some preparations are required. First, mock the fetchAdditionalUserRepos function from the networkActions module along with its response, as shown in the image below.
Since it's a promise, the mock will be handled by the mockResolved function.
With everything prepared, we can write a test to ensure extra data is correctly fetched after the component mounts. The test code is presented in the image below.
Redux Thunk — testing async actions is easy!
In this section, I’d like to focus on testing asynchronous actions. Those actions are handled by Redux thunk middleware. Testing them is more complicated, especially for the first time. I’ll also show you how to mock the axios library to return resolved or rejected values that are needed.
The main goal is to ensure that all expected actions are correctly executed when the fetchRepos function is called.
Testing preparation starts by mocking a Redux store via the configureMockStore function from Redux-mock-store library. An example of a Redux store mock is shown below.
Next, we have to mock the axios library to imitate the correct network call. Since only the get method from the axios module is needed for this test, only this method will be mocked. The image below presents a mock get method from the axios library.
The next step is to prepare a variable with expected actions and their payload. The image below displays how the expected actions array should look for the given example — fetchRepos test.
Once all mocks are prepared, it is time for a thunk action test. This test will check if all asynchronous actions were called in the correct order and carried an expected payload. It will also ensure that the axios get method was called before any Redux action. Example code for the thunk action is displayed in the image below.
Redux Testing — make your app state great again (and bug-free, of course).
This section will focus on the reducers testing. Every Redux testing scenario should start with an initial state check. The image below displays an example test for checking the correct value of the reducer's initial state. Example code for an initial state test is shown in the image below.
Reducer tests for all action cases are mandatory to ensure that every Redux action listener acts as expected and the action payload is correctly handled. A test’s job is to check if the returned, updated state value is the same as the one saved in the expected state variable. Those kinds of tests are very straightforward and help maintain app state management. An example of the reducer test code is shown in the image below.
Snapshot testing — should we even care about it?
Snapshot tests compare the JSON image of the rendered component with the JSON image created during the previous tests.
As opposed to end-to-end tests, snapshot tests are run in a command line runner rather than a real browser. It is a good way to ensure our component looks exactly as we want. It’s useful especially when the look changes dynamically depending on passed props.
Even if they’re not popular among the React community and often skipped, it’s good to know what they are, what they do, and of course how to write one.
The image below displays code for a snapshot test. The renderer creates the method that returns a component snapshot which can be further transformed to a JSON object. The JSON object format is required by the toMatchSnapshot function which, under the hood, compares the current component tree to the historical snapshot saved in the snapshot file. An example of a snapshot file can be found here.
Conclusion — receiving another bug report makes you mad? Consider testing!
This knowledge should help you start code testing using React and Redux. With your code test covered, you will get rid of struggles with the same bugs which reoccur from time to time. If you’ve ever been in a situation where you’re nervous to add new code to an existing codebase in the fear that you’ll break something in a completely different part of the code,
make your and your teammates’ lives easier by diving into the testing world!
Need to check something directly in the source code? Visit: https://github.com/dybik08/github-browser