Avoid bugs & gain confidence when refactoring code by writing tests for your React code using Jest, React Testing Library & a Test Driven Development approach.
In this post I’m going to discuss how to test React applications using jest and react-testing-library.
When writing tests, in the beginning, you may feel that development is slow. However, after a few weeks, the code will be highly maintainable and will allow for the ability to add / remove features with the confidence that new features won’t break the application. This saves time by avoiding regression bugs and making on-boarding of new team members to the project easier, as they can read the tests and easily understand what the code does.
But what should we use to test our React applications? A few months ago we tried using Jest with Enzyme to test our apps and we found it a bit tedious. That is because Enzyme gives us the tools to test the implementation of each component, which is not what we want. From my point of view, I understand that implementations should be checked in pull requests and code reviews, but not using unit tests. Of course, you can test it if you want.
For example, testing the implementation of a component using Enzyme could be to check if the componentDidMount of a component has been called.
In this case, we don’t want to check if componentDidMount was called (implementation); what we want is to check that our component has what we expected when it is ready.
For me, that means it doesn’t matter how it was done, what matters is how it works and how it is shown. When you buy a car you don’t check how the brakes work, you just press the brake and you expect the car to stop. We take the same approach to test our components with react-testing-library.
React-testing-library is a library developed by Kent C. Dodds, which uses the DOM Testing Library as its core, enabling us to query DOM nodes and check what it contains as well as interact with them (firing events for example). It’s part of the testing-library family, which means that there is also a testing library version for Vue, Angular, etc. Writing tests using this library is simple, but before we write test cases, let’s talk about TDD.
Often when we start to create a component, we just read our customer requirements, check the design, create a MyComponent.tsx (or jsx) file and start to write the implementation. Once we have finished, we start to create tests to check the functionality.
That would be fine, but in most cases doesn’t give us a global vision of what we need to do and which cases we need to check in order to avoid bugs later.
Using TDD we will write the tests first, and then we will write the code to pass the tests. So a good rule of thumb is to check the customer requirements first, then write a test case for each requirement. You will notice that while you are writing test cases, you will discover other cases. This approach also forces you to consider boundary cases more carefully which will avoid issues later.
For example, let’s suppose that our customer wants the following simple feature:
Starting from that point and using TDD we start creating our tests file, our component file (empty) and write some tests cases that will check to make sure our customer requirements are accomplished:
Gist file: sum1.test.tsx
But … this forces you to think about a few things before you write a line of code:
So at this point, you can talk with your client and ask those questions. In doing so, you clarify the task you have to accomplish and control the scope. At the same time maybe your customer will more thoroughly consider behavior he doesn’t want or add some more test cases.
So let's write the tests for our component using react-testing-library. If you create a project using create-react-app, just add @testing-library/react as a dependency and import it into your tests.
The main utility we have is render, which enables us to render our component. The second utility we need to use is cleanup, which unmounts React trees that were mounted with render before each test is run. The last utility we will use is fireEvent, which enables us to fire events. Pretty straight forward.
Gist file: sum2.test.tsx
After run npm run test:
Our test should be in red since we haven’t written the code yet. Let’s make our test pass:
Gist file: sum1.tsx
Now we need to test that our component has two inputs. We can do it using different approaches since react-testing-library gives us many ways to query the DOM. My favorite way is to query using test-id, that way it doesn’t depend on the style or the content of the element.
So let's write our test first:
Gist file: sum3.test.tsx
Using getByTestId, we can look for any element with the data-testid attribute that matches the value. So in our component, we will do the following:
Gist file: sum2.tsx
Now we can do the same with all the other components. Notice that at this point, style and behavior do not matter. We are just testing that the component does what our client wants. We simply create tests that check the behavior, to do that we will use fireEvent and extend our expects to check the text content by importing “jest-dom/extend-expect”:
Gist file: sum4.test.tsx
Then we update our component:
Gist file: sum3.tsx
But hey, unexpected value!
As you may have noticed, what we are doing is concatenating strings and not summing numeric values. Let’s quickly fix that by adding parseInt when calling the setValue
And now our tests pass!
So we haven’t checked the UI, but we can be sure that 1 + 1 equals 2.
But what if we try to sum decimals? 1.5 + 1.5 should be equal to 3 but …
That is because we used parseInt. Let’s change it to parseFloat and now our tests will pass:
This is a very basic example, but it shows how the automated testing approach helped us get the component to behave as we need it to by testing for edge cases and appropriately handling data types.
Here is the Codepen that shows our simple example: https://c8sz2.codesandbox.io/
Tests are code too! Sometimes they will need to be debugged as well. Some IDEs have it integrated but, in this example, we’ll use chrome.
If you have questions about this blog, shoot me an email <info@experoinc.com>. Happy testing!
Gist URL (Please fork it from the expero blog account):
https://gist.github.com/ivanbtrujillo/da26337cd63446e58d745ca4dd310ebd
Tell us what you need and one of our experts will get back to you.