There are a lot of discussions in the web when we talk about testing. And even more about unit testing. In React.
This is going to be a brief summary of what and how should we think about when we test a ReactJS application. More specific, what should we write tests for, when creating React components for our app. If you want to skip the reading, here you can find shorter version in a few slides.
All of the points below are recommended by people smarter than me. I just collected them as a summary of best practices, trying to help my team to decide on the matter. And start using them in our day-to-day work.
Do we need unit tests?
This is a long time discussion, that has only one right answer. Yes! Tests provide developers with confidence. This confidence allows us to produce software with better quality. To do proper refactoring. And to reason better about what we do.
In short term it might look it’s not worth it. That it takes too much time. Time we might spend for implementing features instead. But very soon after the start of every project, the payoff of having a nice test suit is huge. The punishment of not having such, especially when a release date is approaching — disaster!
Test render logic
When we write tests for our components, we write them in such way that they test only the render logic and don’t care about any internal implementation. What does this mean? It means that, your test should be testing the things that the user will be seeing and interacting with. Let’s say you are testing a component that displays a button on the screen.
When the user clicks the button, your component renders a message in a div element. Your test should check practically for the same — check if the button is clickable and if this div exists after the clicking.
No lifecycle methods
Tests should not test lifecycle methods, they are supposed to be covered by React itself. If we have some logic that needs to be tested in these methods, we should try to extract it in another testable place and only use it in there. Ideally, in our componentDidMount method for example, we should have only functionality for fetching data (calling an API for example). All the rest that you might be tempted to leave there, should be extracted in other methods, that could be tested, if needed.
(Unit) Tests should not test interaction with outside world!
The name “unit” implies independence and autonomy. A unit test should be an encapsulated piece of code, that has inside everything it needs to perform its job. When we need to write tests which need data, we use mocks. That said, every interaction with APIs, DBs, etc., is not a matter of unit tests and should not be tested in such.
Small, smaller, easier
Tests should be small and easy to read — if we need to test a big component with a lot of dependencies, we need to consider splitting it to smaller testable pieces.
Dīvide et imperā.
As the famous quote above implies, it’s a lot more easier to deal with small, well modularized components, than the opposite. Also, if we need to use big mocks, we need to extract them out of the test and only use them by importing, in order to avoid polluting the test file.
- Given a simple component containing a button and a text field
- We should test what the user sees — rendering, and what the user can do — user interaction
- If there is visual change after clicking — test for it
- If there is a value returned after clicking — test for it
But we don’t care about the internal implementation of the click handler!
I have prepared a small example, trying to demonstrate the above. You can find it here. In order to run it locally on your machines, just clone it and install the dependencies. Then do ‘npm run test’. 🚀 📜
There are many resources on that subject online, but I am going to list here the ones I have found very useful and learned from them.
👏 A nice article on the subject
👏 Another good one for testing with Jest, Enzyme and Sinon
👏 Presentation slides online
👏 Github repo with working examples
The important lesson we must keep from all this testing stuff is, that no matter how exactly we write our tests (well it matters…a bit 💬), as long as we write tests at all. This way of thinking and working should become our second nature when we are striving to produce errorproof software. With time and practice the things become better and easier. The most important thing for us is to never stop improving.
🔥 Thanks for reading! 🔥