By writing tests, at the beginning you may feel that development is slow, but after two weeks / one month, the code will be highly maintainable, as we can add / remove features with the confidence that new features won’t break the application. This is a big impact to the business, because of the time savings by avoiding regression bugs. Another big efficiency is the on-boarding of new team members to the project is 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 your 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.
That means for me 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 it is simple, but before write some 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 our functionality.
That could be fine, but in most of the 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 way to go is check the customer requirements first, and 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 our customer requirements are accomplished:
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 customer and ask those questions. And in doing that process you can clean the task you have to accomplish and properly bound scope of your task, and at the same time maybe your customer will more thoroughly consider behavior he doesn’t want or some cases to add.
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 dependency and import it in 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 enable us to fire events.
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:
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, because that way doesn’t depend on the style or the content of the element.
So let's write our test first:
Using getByTestId, we can look for any element with data-testid attribute that matches with the value. So in our component, we will do the following:
Now we can do the same with all the other components. Notice that at this point, the 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”:
Then we update our component:
But hey, unexpected value!
As you may have noticed, what we are doing is concatenating strings and not summing numeric values. Let’s quick fix that by adding parseInt when call the setValue
And now our tests pass!
So we haven’t checked the UI, but we can be sure that 1 + 1 will be two.
But what if we try to sum decimals? 1.5 + 1.5 should be equal to 3 but …
And that is because we used parseInt. Let’s change it by 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. what I want to put in value is that without the test, if we never have checked a decimal value in the UI, we will never noticed that our component was broken.
One of the intentions of writing tests is to add strange cases to check how our component behaves.
Here is the Codepen that shows our simple example.
Tests are code too! Sometimes they will need to be debugged as well. When you create tests, in some cases you need to debug them to check why it gives you a red. Some IDEs have it integrated but in our case we use chrome.
That’s all! happy testing!
Tell us what you need and one of our experts will get back to you.