Lessons learned testing React & Redux apps with Jest and Enzyme
If you don’t work at a primarily tech company, it can be a bit difficult to convince managers of the importance of things like unit testing. After being a enthusiastic supporter for quite a while, I got the go ahead on a few projects to set up unit tests and try to establish some patterns for my coworkers. Here are some things I’ve learned throughout the process.
Setup is likely non-trivial 😐
If you’re using something like create-react-app or your company has jest and enzyme set up already then awesome! You get a 🎉
If you’re starting from scratch like me then there are a few things you should know:
- Jest runs node, and it runs on your raw file code. Not whatever webpack/etc bundles for you. This means that if you want to use ES6/etc, better declare a test .babelrc setup.
- Jest uses node’s conventions, not the browser’s, so you may need to add polyfills for things like importing/exporting
- The latest enzyme requires a setup file that jest needs to know about.
- You’ll need to have a bunch of mocks for things like fonts/image/styling files
- If you want to run tests in CI you’ll probably want a testResultsProcessor and will likely have to pass — runInBand to Jest (I had to for Bamboo)
The biggest hurdle for me usually is getting the right polyfills so I can write my tests the same way I write my components (ES2017, import/export, etc). Upgrading to React16 was also a bit interesting but I think most of that has been smoothed over (or at least documented) now.
If there’s interest I can walk through my setup in another post sometime. Because of the unique nature of some of my projects it was a bit more of a pain than the docs let on.
Use Snapshots (preferably with a json serializer)
Snapshots are great 😃, but the default behavior of:
Is to spit out the raw output enzyme uses 😦. I recommend using something like enzyme-to-json to make it a bit more readable (very helpful when reviewing changed snapshots).
Also make sure you’re checking in your snapshots to source control, they’re pretty helpful when determining what you change that suddenly broke something.
Jest — watch is great 😃
Make sure you use it when testing! It makes things much easier, and you can pattern match (hit p then type part of your file/folder name) to make sure you’re not running a ton of things every save.
Make sure you don’t close off your test() statements early.
This one can be particularly scary since it’s so easy to do and gives false positives. I only noticed that I had this copy and pasted for one section of my unit tests when I made an error and realized the test didn’t fail.
Which leads me to…
Make sure your tests fail
If you’re awesome and use TDD, then props to you you’re already doing this 🎉
For the rest of us, unit testing is way too often an afterthought and it’s easy to write a bunch of tests and be like “Good, they’re working! Time to move on.”
However this leads to errors like the aforementioned one, and it’s surprisingly easy to grab and test the wrong element or mocked function — especially if you’re copy pasting from a similar test.
It’s not hard to just comment out an essential section of a test and make sure it fails, and it can save you a lot of headaches.
Design repeatable and extensible testing patterns
Especially if you’re not the only one maintaining or creating tests for your project, you want to make sure that it’s as easy as possible to whip up a test for a new component and at least have some base level testing.
One of the biggest hurdles I see to my fellow developers testing is they’re not sure how to get started. Having an obvious, easy to follow pattern helps them out.
For example, if you have a lot of user input cases you want to test components with, consider writing something like this:
Then if additional user input cases need to be tested later, it’s easy to just add to the array. It’s also easy to copy to another component with a similar need for testing.
In fact all of the little conventions I’m following here (beforeEach mounting a wrapper, what I name things, spreading a props object) are great to have defined somewhere so people can easily understand and copy test functionality for their own components and needs.
This is a bit of a contrived example (I usually have pure functions handling input parsing changes like this that I test separately) but it does allow you to know quickly if the whole system is working, and if you aren’t doing complicated input parsing could be adequate for your needs.
Enzyme: Mount vs Shallow
Shallow is kind of the “pure unit testing” approach, it mocks components/DOM that the component you’re testing uses so you can focus on just what your component is doing. Because of this it’s also more performant.
That sounds great, however you have to be a bit careful about your goals here. Are you going for total unit test coverage and complete separation of components? Or are you trying to test a bunch of cases you know might be problematic and want to make sure the component won’t fail?
For the former case, Shallow components are probably perfect. However, for the latter Mount might be more ideal.
Mount fully simulates everything below your component, which means you can do things like grab a child (a custom input component perhaps) and then simulate a change to it’s input to test your component. This mimics how a user would be using it, but keeps the test isolated from the parents and surrounding structure of said component.
Mount also simulates React’s lifecycle methods, which can be pretty helpful for complicated components that make heavy use of them.
Example Tests for a Text based Component using Mount:
(Thanks to Ying Wang for catching a mistake here: Shallow will allow you to simulate html elements within your component, it just won’t do that with that component’s React children. I’ve changed the article to reflect that).
This approach does blur the line between integration and unit testing, but as Guillermo Rauch said:
Kent C. Dodds proceeded to write a whole blog post on it that’s pretty convincing.
I think there’s good cases for both types of testing, but in most of my applications unit testing time is a luxury and I need to be focused on testing actual functionality rather than boilerplate or implementation details. It’s something I’m still trying to find balance in.
I’ve found that now I default to using Mount, and only resort to Shallow if I am encountering performance impacts. Since the APIs are slightly different it just makes it easier to remember how to test certain things.
Testing with Redux is… interesting 😕
On the one hand, actions and reducers are extremely easy to test. They’re both just functions after all and reducers should be pure so no side-effects are present.
However, if you use thunks or sagas things can get a bit more interesting, and connected components complicate things further.
Thunks and sagas will typically dispatch other actions and are also a common area for asynchronous operations. This means that you’ll need some sort of mock store to see what actions were dispatched, and mocks for any calls you’re making (since you probably don’t want to be calling your database/etc every time you test).
Sagas at least are designed to be testable and have some nice features, but thunks leave you hanging until they do whatever they needed to.
This all begs the question: Where do you draw the line on what you test?
There are some great resources out there for fully testing every part of a Redux app, but at what point are you really just testing Redux instead of your own code? Redux is fully tested, there’s no need to reinvent the wheel here. Plus it’s really complicated to test things like connected components. You could end up mocking large portions of your app.
I’m quite confident that if I dispatch an action, the Redux store is going to get it. What I really care about is not if the store got the action, but if the component listening to that action picked up on it and changed something. Which is integration testing territory.
So here’s what I’ve started doing:
- I test the reducers. They’re easy to test and have important functionality.
- I test the unconnected components. This allows me to make sure they’re reacting to events appropriately. It also makes testing quite simple and separates rendering vs data concerns
- I try to extract any complex data manipulation to pure functions and test those
- I use end to end (Selenium) and integration tests to check on sections of my app to make sure it all fits together.
It’s not perfect, I’m still working on finding the best ways to do the last two points, but it keeps things simple and makes me feel productive when writing tests.
Every time I’ve shown a coworker a fully tested Redux connected component, their eyes instantly start glazing over. There’s a lot going on and a lot of mocks to deal with. Taking that out of the process simplifies things and puts more emphasis on the types of tests your user (and likely manager) cares the most about. Given how little time I’m given to create tests for most projects, it’s been a good tradeoff for me.
I’m still learning how to test things properly, and I’ve read and watched a lot of different things on it over the last year. Once you have a pattern established, unit testing with Jest and Enzyme is surprisingly painless, especially if you hand off the more complicated aspects of Redux to integration and end to end tests.
If you’re having trouble with Jest or Enzyme, I highly recommend checking out Reactiflux, their Jest channel has been quite helpful for me.
If you’re just starting using enzyme and Jest, check out this great guide for getting started.
If you’d like more info on Snapshot Testing, check out this guide.
Also be sure to check out the official React, Redux, and Jest documentation. It’s all top quality.
Hopefully this was helpful to your testing journey. Let me know if you have any questions or comments. You can reach me on here or on twitter @Tetheta
Written with StackEdit.