In the past couple of years I’ve been working with mainly React. I embrace unit tests for all my components, but based on the type of component and how complex the rendering is, there are different ways of asserting output. So I’ve split up my unit test approach for these well-known categories:
- Presentational Components: Also known as “dumb” components. They are not concerned with business logic and rarely have their own state.
- Container Components: Also known as “smart” components. They contain business logic, might maintain state of their own, and orchestrate presentation components.
Let’s start with the building blocks of most React applications, the so-called Presentational Components:
Use enzyme mount and validate output with jest snapshots. Perform contract assertions for your props.
The dumb components main concern is the render output, so mounting and then making snapshots of the outputted html makes a lot of sense to me. The snapshot alone is not enough. We also want to make sure that we handle the input from the outside world correctly. In the example below, we check if the defaults work as expected. If properties and callbacks are provided, we test if they have the expected effects on the output or, in the case of callbacks, are called properly.
Making a snapshot for the scenario where the defaults of the component are used, as well as a snapshot for when props are provided really make it easy to cover all the branches of the code in a nice way.
Lets take a look at container components:
Use enzyme shallow rendering for your container components if you can. Access the child components props directly to test the wiring of your parent-child relations.
Smart components are a whole different game. If you start snapshot testing your container components, and someone makes an innocent change to the output of a small presentational component (without changing the props interface), all containers using that component will have regression failures because the snapshots are invalidated. That makes your tests brittle and might lure you in a false sense of security, because it is easy to just ignore invalid snapshots by updating them with the new version (
Instead of using snapshots, you can just access the properties of the child components directly and manipulate those, so the unit tests are only scoped to the actual logic of the container component itself. In the example below I use shallow testing, but if you have more complex nesting (often the case when using providers, graphQL queries, etc.) you can just exchange
mount and it still works. Just as long as you keep away from snapshots or diving into the actual output of your presentational components.
I hope this gives an overview of how to keep your unit tests scoped, and your mind sane. I aim for that 100% coverage with confidence.