Before we check how to write Unit tests in React, let’s understand a little bit about Testing and what Unit Testing exactly means.
Testing in software development is a process of validating and verifying the behavior of the software before it goes live for users. Any unexpected behavior or error encountered during this process is resolved and fixed. Testing can be performed manually or a developer can automate it by writing tests that run the application to find the errors during execution.
Common types of testing include Unit Testing, End to End Testing, Integration Testing, Acceptance Testing, and more. But the one which is important for our blog is Unit Testing.
Unit Testing is a type of testing where every individual element of an application is tested in isolation. Any dependencies that are required for that component are mocked.
The main purpose of writing a unit test is to find out if a component behaves as it is expected and meets the requirements. Any error identified in this stage is crucial because it saves the time required for debugging later.
As we discussed earlier, everything in React is made up of components. Each component has it own state and performs some specific action on the UI. Failure or error in one component will cause side effects on its child components. Hence testing the behavior of each component in isolation is necessary. React Testing Library does just that!
The React Testing Library is a lightweight testing package built on top of the DOM Testing Library. This library contains all the utilities and helpers to test React Components in a User-Centric way. The React testing library is a replacement for AirBnbs Enzyme.
The Enzyme Library was used to test the component's state, props, and internal implementation details while React Testing Library only tests the DOM nodes and UI Elements. If you are using Enzyme in your application, it is possible to Migrate from Enzyme to React Testing Library.
In React Testing Library we are not concerned with how the component behaves internally. We are only looking at how the component interacts with the user. So the library will not care if the component takes any route for finding the solution if the end result matches what the developer wants. Hence even in the future if a developer wishes to refactor his code, the test won’t fail if the output is still the same!
React Testing Library and Jest are not alternatives to each other. Both perform different tasks and work together to perform unit tests on React Components.
Since there are a lot of options for testing libraries to choose from, it is necessary to compare react testing libraries and find the one that fits the project requirement.
Now that we have understood Unit Tests in React, let’s move on to our next section and write some tests.
Going into this demo, we expect you to have a prior understanding of React. If you are new to React, we highly suggest you learn and get comfortable with it before you move to testing.
All the code that we will see is already present on my GitHub. You can access it here.
Let’s set up a new React application using ‘create-react-app’
The reason why we are using ‘create-react-app’ is that it comes pre-configured with React Testing Library and Jest. It even has a sample Test case written for us.
There are other ways of creating a react app but in those cases, we would need to install the Testing packages separately. If you set up your application on your own make sure you install the React Testing Library and Jest.
In our React application we can see that an ‘App.test.js’ file is already present in the src folder. This is a sample test file provided by ‘create-react-app’.
To run this test, we will open the terminal and type ‘npm test’ or ‘yarn test’. This command will run the test in watch mode.
You can see a lot of options here to run the test. Typing ‘a’ will run all the tests from the application.
Our first test is a success! Now let’s move on and understand what a test in React looks like.
A test file in react ends with ‘.test.js’ or ‘.spec.js’. When running tests, the application finds all the files with these extensions to run. Additionally, we can also denote our tests file by placing them inside a ‘__tests__’ folder.
Mostly developers use a ‘.test.js’ file to write a test. This file is always kept alongside the Component JSX file. A common convention is creating a folder by the name of the component and keeping the JSX, test, and CSS files of that component inside the folder.
- - - Components
- - - - - - Component-Name
- - - - - - - - - Component.jsx
- - - - - - - - - Component.test.js
- - - - - - - - - Component.css
We use a ‘test()’ function to write a test case. The test function consists of three parameters - the name of your test, a testing function, and a timeout for asynchronous tests. The default timeout is 1000ms.
A test case can also be denoted using ‘it()’. Both ‘test()’ and ‘it()’ are the same and do the exact same thing.
Inside a test case function, we render a component on which we want to perform tests using the ‘render()' method. For selecting the elements from the component, we use queries provided by the Testing library. These queries consist of two parts. One is the variant and the other is the search type. For example in our sample test case we use a query ‘getByText()’, here ‘get...’ is the variant, and ‘ByText’ is the search type.
The below table shows the 6 variants of the query.
To summarize, when we want to get a single element we can use the getBy query. But this query will give an error if the element is absent. Hence in cases where we want to assert that the element is not present, use queryBy. For asynchronous operations always use findby and findAllBy. For getting multiple elements, use getAllBy, queryAllBy, and findAllBy.
Search types are used to find the elements based on some criteria. Below are some common search types.
After we query the element which we want, we can assert some statements based on the test cases. A single test case can have multiple assertions. To make assertions we can use ‘expect()’. The queried element is passed as a parameter to the ‘expect()’ function and a method is called which specifies the condition for assertion.
In the above code, we are expecting the ‘linkElement’ to be present in the Document using the ‘.toBeInTheDocument()’ method. Similar to this method, we have numerous other methods to check if present, if not present, if true, if false, and more. Some common ones are mentioned below.
A ‘describe()’ block represents a test suite. A test suite can have one or more Test cases. It is not necessary for your tests to be inside a suite. As a standard practice, all similar test cases are kept inside one Test Suite.
Now that we have understood what a test in React looks like, it’s now time to write our first test.
We will create a component folder that will have all our Components and their test file. Inside the components folder, we will create an Application Folder for the application component. Inside this, we will add an ‘Application.js’ file and an ‘Application.test.js’ file.
Let's add some basic HTML inputs and some heading text inside our ‘Application.js’ file to test.
Now let’s move on to the ‘Application.test.js’ file and write our first test to check if the heading “Login Form” is present on the screen
As you can see in the above code, we have rendered the Application Component, selected the heading element by ‘getByText()’ query and asserted it to be in the document using ‘.toBeInTheDocument()’ method. Before running the test we will delete the sample ‘App.test.js’ file to avoid confusion. Now let’s run the test and check if the test passes.
Voila!! Our first test ran successfully! Just to make sure our test is asserting the text correctly, We will change the heading from ‘Login Form’ to ‘SignUp Form’. Now, let's run the test again.
Our test did fail! If we check the log, we can find the exact reason why the test failed. This will help in debugging the code once the application grows.
In some cases, we might not have the exact text which you want to test. In that case, we can make use of regular expressions.
For example, in the below code login form will be selected and the case of the text will be ignored.
Let’s write another test but this time using ‘getByRole()’ query.
In the above code, we are testing if a Login button is present on the screen. We are selecting the button by the role ‘button’. Every element in HTML has a specified role. For example, <h1>- <h6> tags have heading role, <button> tag has button role, etc. You can find the entire list of roles here.
Let’s write some more tests with variations of the query and run the tests.
Up until now we have done tests on the elements present on the screen. In this section, we will test the UI after some user interactions. For this test, we will create a separate folder with the Counter component.
We have added some basic counter logic which will increment the state variable with every click of a button.
We have added two tests, one which checks if the initial count is zero and other that checks if the button is present on the screen.
Now let’s write a test that will check if the count is updated once the user clicks on the button. To test such user interactions, we can use ‘fireEvent’ from the ‘@testing-library/react package’.
As shown in the above code, we are selecting a button from the UI and firing a single-click event using fireEvent. Since we click the button once, the initial count will be incremented to 1 and hence the assertion is true and our test is passed successfully!
Debugging is an important feature and can save a lot of time and effort while bug solving. Fortunately, the @testing-library/react provides us with enough methods to debug our tests.
We will modify our test from the last section as below. We have added a screen.debug() method after the render method. This method shows an entire DOM structure present in the component.
Never Commit your debug statement in your code. Always remove the screen.debug() statement.
Another useful tool for debugging is the Testing Playground Chrome extension. You can use this extension to find if the element is present, and how to target them accurately. Once you install the extension you can open it from Chrome Dev tools.
Asynchronous operations take time to finish their execution. Fetching data, sending data, saving data, and waiting for a timer are all examples of Asynchronous Operations. In this section, we will see how we can test components with Asynchronous actions.
We will create a CounterByDelay folder inside the Components Folder. Our‘CounterByDelay.js’ and ‘CounterByDelay.test.js’ files will go here.
In the above code, we have added a ‘delayCount()’ function which sets the count + 1 after a delay of 0.5s. Let’s write a test that waits for the count to update before testing.
Here we are using a waitFor() function from @testing-library/react which waits for the count to update.
Another use case of Asynchronous tests is testing an element that is currently not inside the Component but will eventually be added.
One of the common responsibilities of the UI is to send and receive requests from an API over HTTP protocol. We can write a function to test the API but that would result in a lot of unnecessary API requests. In case your API is billed for every request, you will be billed for all the requests that you sent just for testing. In such cases, we can mock an API and test the response.
For mocking API while testing, we will use a package called mock-service-worker.
Let’s start by installing msw.
Once msw is installed, we need to create a component that sends a get request to the API. In our case, We will be creating a Users component which will get 10 users with the help of JSON Placeholder API.
We have created a simple Users component which will render a list of users and will show error in case something goes wrong. Now before we start writing tests, we must set up a dummy server and a handler that will handle our requests.
I am creating a mocks folder in src which will have two files. Let’s add our first file ‘server.js’ here.
You can find this code in the official documentation here.
Let's create a second file named handlers.js in the same folder which will handle all our HTTP requests and responses.
This file exports a handler which has a rest.get() function with two parameters. The first one is the API that we want to intercept and the second is the handler that mocks the API. For mocking, we are using an array of 3 users which resembles the response from the JSON Placeholder API.
Our final change is in the ‘setupTests.js’ file. Replace the existing code with the below code.
In the above code we are setting up a server for all the requests, after every request, we are resetting the server and after all the tests are performed the server gets closed.
This is what our folder structure looks like
Now with all our setup done, let’s write some tests for testing the API.
In the above code we are testing if the API returns 3 values. The 3 values are the ones that we are sending through the mock server. Let’s run the tests.
As you can see, all the tests passed successfully. Just to verify if our test is working, change the value to have a length of anything other than 3. We will change it to 4 and now our test should fail.
Great! This means our Mock server is working!
Just as we tested the response of the API, it's always a good practice to test the error handling. If you check the Users.js code I am already handling the Error using setError(). This means in error cases, the component should render “Error while fetching users” instead of the users. Let’s write a test for this scenario.
In the above code, we created another test that will test the error. Here inside the test, we are setting up a server that returns an error with status 500. Then we are asserting the Error text to be present on the screen. Let’s test this test case.
Code Coverage means how much your code has been executed while running the test. Consider this a kind of report consisting of all our test cases. In order to create a report we must first add a script in our package.json file. Open your package.json file and add the below line in the scripts object.
The above line will add a coverage script for your project. In order to run the script you can type
We now have a report on all the files and the test coverage. But if you see closely some unnecessary JS files are also present. We can ignore them by adding an extra flag at the end of the command.
Now if we run the command again. A new coverage report will be generated with only the files present in the Components folder.
You can find all the code on my GitHub.
Now that we have understood the React Testing Library, let’s see some best practices we should follow.
Unit Testing should never be avoided if you want your application to have minimum errors and defects. The React Testing Library is a great package to test React applications by generating tests that closely resemble user scenarios.
During debugging React applications it is important to understand where the error occurred or where exactly the customer faced the issue. This is where you can use Zipy and monitor real-time sessions and debug your React code quickly. Zipy.ai combines stack trace and session replay to make it really easy for developers to identify errors and debug them.
Coming back to this blog, we learned about performing Unit Tests in React using React Testing Library and Jest. We wrote some tests and studied their variations. In the end, we saw how we could create a report for all our tests. We hope you found this blog helpful.