Types of Tests in Jest

Learn about unit testing, integration testing, and snapshot testing with Jest.

Types of testing

Jest supports writing three main types of tests: unit tests, integration tests, and snapshot tests.

Unit tests

The goal of unit testing is to ensure that the smallest pieces of our code, independent of anything else, behave as we expect. This would be something like, “If we’re given an argument A, our function returns true, but if given argument B, the same function returns false” or “When a user clicks a button once, sequence A is triggered, but on the second click sequence B is triggered.”

Unit tests are unconcerned with the specifics of sequences A and B or the consequences of a true or false return value in the larger context. All that unit tests want to know is that in any environment, if we’re given A, we can reliably expect B every single time.

These tests are invaluable because they validate what we think we know about the code we’ve written. There is nothing quite like racking your brain over an “impossible” bug only to realize your simple, two-line function was actually returning false when you’d thought it was returning true.

Integration tests

Integration tests are concerned with the level above unit tests. With these, we are evaluating how our units work together. This can be anything from testing user interactions with a React container that orchestrates multiple display components along with shared state and logic, to validating the flow of our data from one Node layer to another.

These tests rely on sound unit tests and allow us to break up our code into as many small, manageable pieces as we need to without sacrificing stability. If unit tests check the reliability of our bricks, integration tests assess the strength of our wall.

Snapshot tests

Unit and integration tests are ubiquitous throughout software engineering. Snapshot testing, however, is front-end specific.

In front-end engineering, we have the unique challenge of an entirely different way to break things. We have to ensure the application functions from a logic standpoint, the same way we think about testing back-end code. However, in addition to this, we have to worry about the user’s experience with our application. All of the logic can be working smoothly, but if we render a broken UX, it’s irrelevant how smooth our logic is.

For this, we have snapshot tests. Snapshots are completely unconcerned with logic and entirely concerned with what is rendered to the DOM. They rely on saved historical snapshots of the application’s node tree before changes were made, comparing these to the current code’s node tree.

The test is whether these two snapshots are different, i.e, if the application is now rendering in unexpected or different ways. In some cases, the snapshots do differ, but the differences are intentional, at which point it’s time to update the saved historical snapshot. In other instances, it will alert us to an unintended change, at which point we can correct our mistake before merging and deploying.

In both instances, it provides the opportunity to scrutinize the UX built, not just the logic.

Writing testable code

In approaching our code with this in mind, we can focus on writing testable code—code that is easy to write tests for. This approach also tends to naturally enforce other best practices, such as the single-responsibility principle (this function only performs one task, and we can therefore test easily), DRY (once we test this one piece of code, we can use it wherever we want without having to test it again), and more.

As a general rule, if we’ve written code that we’re having trouble testing, we should rethink what we’ve written. We’ll often find that there is coupling where there shouldn’t be, unnecessary complexity, or logic living in the wrong place. For this reason, some developers choose to adopt TTD (test-driven development) to enforce testability from the start.