About a year ago, I published an article with a similar title, explaining how to test React & Redux applications. It’s available here. While I believe it’s still helpful and somewhat accurate, we can do better. Even if you’re just starting, it’s definitely worth reading — I explain some basic concepts of testing React applications. Either way, here are my thoughts.
TLDR: Start with an integration test, and consider unit tests when complexity grows.
In my previous article, I put too much emphasis on testing single building blocks (components, actions, reducers) rather than treating them as a whole. More often than not, the feature you are about to deliver is not complex enough to benefit from unit tests. Also, tests for single building blocks don’t ensure blocks work properly when put together. I used to write tests and then manually verify the result in my browser. Now, with integration tests, I’m far less worried that something will fail along the way. If you’re a fan of the TDD cycle as I am, you’ll find integration tests easier to test-drive your application. Fetch from here, click this, expect that to be displayed. Simple, and all in one place.
What we’ll build
We’re going to build a simple application that fetches one activity at a time and displays a cumulative list. If you haven’t put up a testable React and Redux application yourself, feel free to get some inspiration. The code is available here. We’ll use activities from BoredApi.
Let’s suppose that these are the rules:
- At the beginning, no activity should be on the list.
- When the user clicks the “Random activity” button, new activity is fetched and appended to the list.
See the rules in action: launch the app
Testing first rule
Testing our first rule is rather simple. We mount the component providing a store, and expect that no activity is on the list.
Testing second rule
Now let’s follow with a test for the second rule. First, we need to amend the initial state and put one activity already on the list.
Then we have to stub the API endpoint. When we execute a GET request at a given URL, the activity with a given name will be returned.
Having the setup ready, we can now simulate the click, and assert that a new entry was appended to the list.
That’s it. With only two tests, the whole stack is covered. See how efficient we can be? Again, the whole code is available here.
Making your tests even better
We’ve learned how to test your application the right way. Now, I’d like to offer a few tips for making your tests more robust in a growing application.
Building test state
The application I’m working on at the moment has over 3000 tests. Imagine what would happen if, in order to introduce a new feature, I’d have to refactor the store structure? I bet at least a few hundred tests would fail. Then what? Fix them one by one? Definitely not a situation I’d like to find myself in.
Here’s a solution we came up with. Having the composing functions puts a layer of abstraction on how the store is arranged. Now there’s a single point of change.
Now the usage in tests is easy and descriptive.
Select components using custom attributes
When looking for inner HTML nodes in the mounted component, it’s tempting to create a selector based on their type or class. But should your test fail when you modify class name or element? Hell no — they should have nothing to do with each other.
So instead of
Consider using custom attributes.
Custom Enzyme wrappers
If you’re a seasoned test-driven developer, you know how important readability is. It drives me nuts when I enter a hundred test cases long file, and almost every single one has the same boilerplate inside. Wouldn’t it be awesome to have it all in one place and easily accessible across multiple test files?
A colleague of mine came up with a neat solution and created a library to make it available for others. You can wrap the component you’re testing with your own methods and hide the irrelevant logic inside the wrapper. Here’s an example from the GitHub page.
Thanks for joining
I hope I put some ideas into your head today. Feel free to share your thoughts :)