You Are Doing Jest the Wrong Way

You Are Doing Jest the Wrong Way

Alexandre Gaubert

I wrote Jest tests for over three years on a complex web application.

Like me,  you are probably doing it the wrong way. Without knowing it. I'm not blaming you.

Jest is an excellent test runner for frontend code, coupled with enzyme and react-testing-library for React apps, it can be a potent tool to test your components.

But here is the catch: it should be used ONLY to write unit tests for isolated components. If you feel like you are spending too much time writing a small number of tests, it's probably because you try to miswrite them, and here is why.

Enzyme (AirBnB), React and Jest logos to explain you can combine them to test your components.
Jest, Enzyme and React: a great combination to test your component. Source: Medium

Mocking and stubbing

First, because Jest can only run frontend code, you will have to mock many things to make your app work without the backend. Let me give you an example.

Imagine that you want to test your fancy user list with Jest, e.g., adding, deleting, and editing a user. The first step is to stub all the underlying API calls. For example, you can stub your fetchers using jest.mock() to make them return hard-coded JSON.

Done? It should be good now. Time to run your tests and see the magic happens.

Of course, it's not working.

The image represents a scheme showing the difference between stubbing and mocking
Difference between Stub and Mock. Source: LinkedIn

Your application uses redux to share data across components, you are only mounting your UserList here and you are missing all the redux setup made in your top-level index.js.

There is a little trick here: export the raw component (not the redux's connected one), then import it into your test file, this way you can pass all redux actions and states, which are usually provided by mapStateToProps and mapDispatchToProps.

Are those tests red again? Of course: you are using react-router methods in your component (maybe navigate or useMatch), but your Router component is outside your UserList. Try mounting a fake router in your tests, and that should work now!

Are tests not green yet? Argh, it seems something is still missing…

Well, I think you see my point at this stage.

You are doing it the wrong way

The problem here is that the code attempts to test the entire UserList – which is almost a full page itself. Jest is intended to run unit tests, and here the code describes an end-to-end test.

The more you try to mock and stub everything it needs in order to work, the more you get away from the real behavior of your application. Too much mocking means that you're not really testing anything in the end.

So not only are you giving yourself and your teammates a pretty rough time, but you are also writing unreliable tests. Those tests are also going to waste hours of engineering time whenever you make a change to your code.

Make the test a unit test

The right way to test your UserList is pretty straightforward. You have to move away from a unique, large, slow, and unreliable test, to multiple atomic and quick unit tests.

The first step is to split the UserList component into the tiniest possible components. Following the smart/dumb components pattern, your code should end up with the UserList component only managing the user list state, fetching things, rendering all the sub-components, and passing them just what they need to know to render correctly.

While doing that, you must write sub-components to be used anywhere in your application. The more you make them reusable, the easiest it will be to test them.

Test everything

Once it's done, write tests for every single component. You should now be able to mount any component by passing stubbed data and mocked methods — and it should be far more accessible than before. Since you're saving time, use that time to write more edge cases and assert every case is handled gracefully.

Do not test the UserList

This might seem counter-intuitive, but you shouldn't be testing the UserList component itself.

See it like this: your UserList component fetches data and orchestrates rendering of all the components for which you already have robust tests. If you try to test it, you will end up in the same scenario. For this component, rely on end-to-end testing only.

End-to-end testing

End-to-end testing is a test in which you don't mock or isolate anything. In this scenario, you want to start your application and test it just like any user could do. This way, you make sure that the whole application is working correctly.

To do so, spawn your application and use an automated testing tool that uses a controlled browser to visit the application, perform actions and assert the results. There are dozens of tools built for this, such as Rainforest, Cypress, or Selenium.

This way, you make sure that your code works in real-world conditions. Running those tests in your CI will make you more confident about your code changes and saves you hours of writing inefficient Jest tests.

Rainforest QA logo, a tool to test your front-end code via a controlled browser to visit the application
Rainforest is a great tool to test your frontend in real-worl conditions. Source: Rainforest QA

Conclusion

To make sure that you don't break anything in production:

  • Split your code into tiny, reusable, and testable components.
  • Write a bunch of unit tests with Jest (or any test runner)
  • Write some end-to-end tests which run your top-level component just like in production, perform actions and assert everything is rendered correctly.

If you follow those guidelines, you will be all set to run rock-solid applications in production!