You Are Doing Jest the Wrong Way
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
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.
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.
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
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
Are those tests red again? Of course: you are using
react-router methods in your component (maybe
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.
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
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 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.
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!