Testing with Jest: 15 Awesome Tips and Tricks
This post is now available in Portuguese🇵🇹.
Unlike magicians, JS developers do reveal their secrets! Last year my favorite testing framework was Mocha + Enzyme for testing React applications but then Jest v14 came along and introduced an intriguing new concept: Snapshot Testing. I gave it a shot and immediately fell in love! After using it on a daily basis for 8 months I thought why not share the best tips I’ve learned so far?
Snapshots and mocking, problems & how to solve them
What code to test and what to mock is no longer about defining a *unit* test or *integration* test like the old days, now it’s about what brings the most value. Therefore it’s common that you need to mock a component in a test and that is very easy:
And before you judge me: I don’t normally use .jsx, but GitHub won’t syntax highlight JSX properly if .js is used 😭
Lookie here, for those of you familiar with react-router@v4 you know why this mock is awesome (hint: no more wrapping tests with <MemoryRouter> everywhere, yay!).
What about named imports?
Too easy I know! Jest will serialize a component if you mock it into a string, it don’t care how and if you’d like an in-depth post about how, what and why that works (teaser: it is related to how JSX is transpiled to JS and other cool & nerdy stuff) give me a shout out on twitter.
Target your mocks with surgical precision
What if you import multiple things but only want to mock one of them? Still easy!
In the following example it’s useful to get proper prop-type warnings in your tests even though the rendering is still being mocked. By keeping the proptype warnings working you do yourself a big favor, especially when the day comes and it’s time to update your dependencies as you can do it with confidence. Jest will have your back and tell you exactly what components is affected by any breaking changes 😉
Why is createElement being used here? Again, let me know if you’re interested in going in depth on this topic. The gist is that whenever you need more logic to your mock you can’t simply return a string like this:
const Foo = () => 'Foo'
You’ll get an error as React don’t support components returning strings. It will in Fiber, but it’s still not what you want happening, you want the snapshot generated by jest to look like this
<Foo /> not
Foo. The gist is that when you want Jest to generate a snapshot that is showing the props coming into the component instead of its output and replacing the component with a string isn’t enough returning createElement is your friend.
TL;DR use createElement when a simple string isn’t enough.
Code examples are better than a thousand pictures, so here’s one more to let the concept sink in:
The clue here is that using require.requireActual inside the mock factory lets us get the original module and override only the parts that’s needed. And since this use case didn’t need proptype validation we got away with replacing
LoadingBar with a string and createElement wasn’t needed. Lastly we didn’t mutate anything here like the previous example so we can just spread out the ReactReduxLoadingBar original module.
And now the elephant in the room, using toMatchSnapshot on the dispatch mock calls instead of
toHaveBeenCalledWith(showLoading) say whaaaat??? If you’ve never tried using snapshots for other things than rendered React components I got news for you! Just like with React components there are code that you don’t need to know much more about beyond wether it accidentally changed or if it throws an error. The beauty of snapshots is that it makes your code much more refactor friendly. Replaced an action creator? Changed the data shape of a reducer? Changed the arguments signature on your callbacks? Stop updating all that stuff in two locations everytime.
Just remember, with great power comes great responsibility!
Mocking components with render callbacks
This works for other similar use cases as well, like the popular render func pattern.
Mocks impersonating the original, key to finding the right balance
With Jest it’s possible to reach 100% test coverage in your own code. It requires a good balance between what functionality is worth reimplementing in your mocks and what isn’t. In the previous example the render callback behavior of
Route was implemented so that the callback got executed and snapshotted. The next example takes it one step further.
Again, in this case it’s useful to execute the children function in
SidebarLink but it’s not necessary to render
SomewhereElse, in those cases Route will be serialized in the snapshot the same as
Protip: Find yourself writing the same mock over and over again? If the mock is placed in
<rootDir>__mocks__/react-router-dom.js you can end the madness:
That’s “Zero configuration testing platform” in action 😎.
Stop hunting proptype warnings, let Jest do it for you
The best solution is to use FlowType for this kind of thing, as it does far more than what React is able to do with the built in proptype checking. FlowType doesn’t require you to specify wether a prop is required or not because it understands your code. And on top of it all it lets you typecheck your state, take a look. If that doesn’t float your boat there’s a plan B which is just a small tweak to your config (probably easier to get that PR approved since static type checking is still a very new concept to many):
And just like that tests that have proptype warnings no longer pass! FlowType lite™, but 2% of something is better than 100% of nothing, right?
Catch the sneaky unhandled promise rejections
Since Jest 20 have a lot of new promise related features this problem has become more relevant recently. In Node v7 (and probably v8 which is the next LTS release set to October replacing the current LTS v6) unhandled rejections will throw an error which will fail the Jest test. This is very good as unhandled Promise rejections are just as bad as any other error being thrown, they should never go under the radar. The next example shows how to backport the same behavior to your jest setup when you’re on a Node version older than v7:
“Hold up, stipsan; I thought setupFiles run before each test in a sandboxed env, how can a memory leak happen?” good question, you’re right that every test is isolated. But process is a special variable provided by Node that is shared between tests, even when running tests with multiple workers. That is why it’s necessary to add the guard against leaks. If you think about it there’s sense to this. How else would it be possible to
$ NODE_ENV=test jestin your terminal and somehow all your tests can access
process.env.NODE_ENV and see that it’s value is
test just like you expected?
setState and event handlers, getInstance to the resque!
The object returned by
renderer.create() have in addition to the familiar
toJS method a couple more:
update. Digging into
Yes you can also access children props and go deep in the tree, but before doing that it’s worth a rethink. I still haven’t seen a use case where traversing the children prop didn’t have a better alternative.
Mocking Higher Order Components (HOC)
To determine the best mocking strategy look at what your component is using from the api surface of the HOC. In this example we’ll look at the
react-redux, which is a very complex component with a lot of functionality in its own right but since our component is only using it to access the
dispatch api on the redux store our mock don’t need to be complicated at all:
There are times when it’s not enough with a simple mock like that. In redux-form for instance lets you pass a
validate prop that have access to form values as well as props passed to your component. We’re gonna use that to make the validation strings translatable and make sure the mock is calling
validate with the props to ensure validation don’t suddenly start breaking after a refactor without the ci catching it.
Testing lifecycle hooks when getInstance can’t help you
getInstance will allow you to test most of the lifecycle hooks that is available to a component that use
setState. But what about components that use props instead of state with lifecycle hooks? Lets look at how we can test wether a shouldComponentUpdate hook is implemented properly by the component using the
update api from
How to test unmount logic
“setState cannot be called on unmounted components”, ever seen that error? How does one go about testing logic for when a component is unmounted? Here’s how:
Globally mock components that rely on Context, testing react-intl like a boss
The grand finale, combining the previous tips + a few more tricks to mock very complicated components. What is more complicated than translating your UI and localizing it? Testing them! I’m gonna use react-intl as the example implementation. In the official documentation it’s recommended to write a wrapper to
createComponentWithIntl. The upside is that it makes the Formatted* components output the same as they will in production, making snapshots a better source of truth. The downside is because it wraps your component in an IntlProvider it will when you call
component.getInstance()you will get an instance of
IntlProvider instead of your component. The example below shows how it’s possible to use a global mock as a better solution, providing the benefits of
createComponentWithIntl while avoiding changing the behavior of things like
getInstance in any way.
And on that bombshell it’s time to wrap up 👌. Thanks for reading, I hope you learned a thing or two and if you have any feedback please share! Constructive criticism is welcome 👍.
P.S. all the examples are formatted by prettier ❤️