TL;DR

We rewrote a large chunk of our tests using Jest. Snapshots replaced a whole slew of Enzyme expect statements and allowed us to more easily test our Redux actions and store. In the future, we hope to make use of some other features of Jest, such as the Multi-Project-Runner.

Old Tests

To understand the motivation behind rewriting some of our tests I’d like to show an example of what our component tests used to look like:

Verifying the markup of a very simple component

This is an example of a simple button that renders a small bit of HTML with the correct icon. In this specific case, it should render as a trash can. Even in this simple example there is a large list of expect statements to verify the component is rendering the correct HTML. Let’s look at another, slightly larger example:

Verifying the markup of a slightly more complex component

There are a couple of problems with this old test. The first is that once again we have to write a whole bunch of expect statements to verify the component is rendering the correct HTML. In this case, we find the input, label, and menu elements for which to run tests against. A second problem is the expect statements in the “renders the input field” test that check the prop values of the input. These tests are completely unnecessary in my opinion because they simply test that React is doing its job passing prop values. We shouldn’t have to test React itself.

These examples are from a couple of very simple components and only demonstrate testing the markup. Now imagine trying to test more complicated components and behavior. Your test files will quickly become unmanageable. If you really want to trigger a panic attack, now imagine a product manager and designer decide a redesign is in order and most of the markup will be changing. You’ll now have to revisit all of these tests and update them line by line.

Jest

Enter Jest. Facebook fancies the framework with the tagline “Delightful JavaScript Testing.” This has mostly been true in our experience. Setting it up is quite simple, and the tests run very quickly. You can choose not to configure Jest out of the box and it will Just Work™, but we did apply some configuration in the form of a jest.config.json file that is applied via the command jest --config path/to/jest.config.json . The small amount of configuration we applied is mostly for telling Jest where to output coverage files and to load with a setup file.

The parts of Jest we find most useful are the expectation objects/matchers, the mocking library, and the snapshots.

Expectations and Matchers

These tests should look familiar to anyone who has used the Chai Assertion Library. The expect function takes a value and returns an “expectation” object. You will rarely (never) call expect on its own. Rather, you will chain a matcher function to assert the value input is what you’re expecting. A simple example from the docs:

test(“two plus two is four”, () => {

expect(2 + 2).toBe(4);

});

There are some useful matchers that help distinguish between falsey values, such as toBeNull , toBeDefined , toBeUndefined , etc.

Mocks

The mocking library has proved extremely useful in our tests. Oftentimes we want to test that after an API call is made we’re properly updating the Redux store and any corresponding React components are rendered correctly. But we don’t want our tests to be reliant on a network request. Making API calls within your tests can greatly reduce the speed at which they run and can be error-prone. Your application shouldn’t be responsible for testing the network and availability of an API. There are monitoring tools that are much better suited for that sort of thing.

All right, let’s see an example!

Mocking the `get` function in the “content_api” module

Typically, we place all the interactions with a particular API into its own file. We then export relevant functions to be used in different parts of the app. In the first block of the above example we’ve exported the get function, which simply fetches data from a defined endpoint using a query string.

In the second block we’re calling jest.mock and passing it two arguments. The first is a reference to the “content_api” module where we’ve defined that get function. The second is a function that returns an object of defined mocked functions. We define our get mock and set it equal to jest.fn , which returns a predefined API response. With that mock in place, anytime the get function is called in a test we can use a matcher on it. One example for if we expect it to have been called with the correct URL:

expect(ContentApi.get).toHaveBeenCalledWith(contentID);

Snapshots

Now let’s re-examine the aforementioned “old” tests and how they would be written with Jest and snapshots. We can replace all of those expect statements with a snapshot (for some reason the Gist wouldn’t render, so here is a regular code block):

it("renders with proper defaults", function () {

const BasicTabComponent = renderer.create(

<Tab title="Awesome Tab" isActive/>

);



expect(BasicTabComponent).toMatchSnapshot();

});

Which outputs a snapshot file with the following contents:

// __snapshots__/basic-tab.test.js.snap



exports[`Basic Tab renders with proper defaults 1`] = `

<li

className="tab active"

title="Awesome Tab">

Awesome Tab

</li>

`;