Jest Testing Your Front-end Code

Yi Rui Goh
Rate Engineering
Published in
7 min readJul 26, 2018

We have recently added Front-end testing to our mobile app here at Rate and this is a comprehensive review of what we’ve learned. It is also a crash course to get you up to speed with front-testing using Jest (I’ll cover the core concepts!).

Image by chuttersnap on Unsplash

What’s Jest?

Jest is used by Facebook to test all JavaScript code including React applications.

As long as you are using React, Jest is almost certainly already installed. Here at Rate we built our mobile app RateS using Expo and it’s no surprise that Jest was already bundled and installed when we got around to implementing testing.

There are many reasons to use Jest instead of other testing frameworks (read the AirBnb migration article if you are curious) but for us they are pretty straightforward:

  1. It comes bundled with Expo
  2. “Zero configuration testing”, well nigh 0 for us due to our stack
  3. Watch mode that only runs tests related to changed files
  4. Snapshots (I’ll get into this shortly)

Getting Started

Setting up Jest for testing is pretty simple, especially if your app is created with Create React App, Create React Native App or Expo. The Jest documentation has a pretty comprehensive Getting Started Guide to get you going.

For our purposes, we are also using enzyme and enzyme-to-json. enzyme is a testing utility that makes it easier to interact with the React Components while enzyme-to-json helps us serialise the enzyme wrappers for Jest snapshots.

You can use react-test-renderer as it is showcased in the Jest snapshot testing guide but you will be missing out on some pretty powerful features that enzyme provides.

Writing a simple test

Run jest --watch or alternatively via a script:

"test": "node ./node_modules/jest/bin/jest.js --watch"

This will automatically watch your files to run new tests or tests related to changed files. Jest automatically finds tests if they are in a __test__ folder or named with a .spec.js or .test.js extension.

Which to use is up to you, but it is best that everyone agrees and adopts the same convention. We placed all test files adjacent to their component files since you’ll most likely be looking at both files when something goes wrong:

├── SearchBarComponent.js      # component
├── SearchBarComponent.test.js # test

Finally, here is what a simple test looks like:

For those who are coming from Mocha/Chai, you’ll find the syntax and naming extremely similar. If you have run Jest in watch mode, you will notice that this new test was automatically run:

Jest has a pretty powerful and extendable assertion library, you can use it to match types too:

Snapshots

Now let’s move on to the defining (arguably) feature of Jest, snapshots. Snapshots, as the name suggests, are snapshots of whatever that you give it. Here’s the quick breakdown of how it works:

  1. Specify snapshot testing of whatever .
  2. Jest creates a snapshot of whatever during the first run of the test.
  3. During runtime, Jest compares the new whatever with the stored whatever saved from the previous time.
  4. If there are discrepancies, Jest asks for your verification if they are intended.

The most common usage is to take a snapshot of a React Component. Enzyme comes into play here with its shallow rendering. In short, it renders components only 1 level deep, so any child components will not be rendered.

Let’s create our first test and component:

After saving, the test automatically passes since this is the first time a snapshot of this component is created and saved as MyComponent.test.js.snap. (This component is just an empty <View /> from React-Native.)

// Jest Snapshot v1, https://goo.gl/fbAQLPexports[`My first test suite! correctly renders 1`] = `<View />`;

Now let us modify MyComponent and see the snapshot feature in action:

Once it’s saved, Jest will inform us that our snapshot test has failed, since the stored snapshot does not match the new one:

Notice that you are the one to verify the code changes, an unreadable snapshot is a symptom of an unwieldy component. Smaller components are easier to test, and any errors are easier to isolate as well, so do yourself a favour.

If you are doing Test Driven Development, Jest’s watch mode is a lifesaver.

Other uses for Snapshots

We also found an interesting way to use snapshots (remember, they take a snapshot of whatever you give it!).

Recently we encountered issues with a mismatched API that somehow slipped past all our tests. And before you jump to comment section, yes, we know that end-to-end testing is what we need. Something like Detox will be immensely useful, but they are not the easiest tools around to configure, let alone implement.

An acceptable middle ground is to take snapshots of the live API responses. Here is a simple example of how we use it:

Do realise that like any snapshots, you have to verify its correctness for the first time. If you need something even more fine-grained and verbose, you can try using the matchers:

If the assertions that Jest provides are inadequate, you can always extend it on your own. But what about all the other cases where you do not want to make live API calls? Let us segue into mocks…

Mocking and Spying

A mock is an object that simulates the behaviour of a real object in controlled ways and is able to verify its usage

We use mocks to simulate the behaviour of any dependencies when we are unit testing. In particular, when unit testing, we want to control all the variables that are not being tested, this is typically an imported module or API calls.

Let’s start with a simple mock function (also see the Jest Docs):

We not only verify calls and their arguments, but can also change their return values, implementations, even promise resolutions. Again, Jest has an excellent guide on mocking async for network calls.

A simple use case for creating a mock function is testing callbacks, where you supply a method with a mock function and then assert how it was called. We find that this is seldom relevant, since in most scenarios dependencies are abstracted into a separate module.

This is where the ability to mock an entire module comes in handy.

Notice that there is no need to specify mocks for each function in a module, jest.mock mocks all functions in the module, but I think there is a case to be made for specifying which functions are mocked for the sake of clarity.

Anything that can be applied to mock functions can be applied to functions in a module. (Interestingly enough, the mobx-react mock is how we managed to get mobx decorators to work with Jest.)

In other cases, you may want to keep the implementation of a function, this is where Jest.spyOn lets you “spy” on a function.

I am by no means an expert in using mocks, if you want to know more (like how spyOn is just sugar-ed jest.fn), read this article by Rick Hanlon II.

All together now

Putting everything that we’ve learned so far lets you create some pretty useful tests. Let’s take a look at an example, Component testing.

We have this screen with a DoneButton that renders when the selected item subscribeCount is more than 3 ( subscribeCount is passed as a prop). Here’s how my test on this component roughly looks like:

Let’s look at the first two tests, enzyme comes into play here by allowing us to directly change the props with setProps and allows us to find child components and their corresponding props too.

The next two tests showcases how we simulate presses on the buttons and check that their bound functions are called.

Setup and teardown functions beforeEach and afterEach help you to DRY.

You can use snapshots too, taking a snapshot each time the prop changes. Whichever you choose (or both), it’s important to be mindful of using snapshots. Justin Searls (see here) put it most succinctly:

Good tests encode the developer’s intention, they don’t only lock in the test’s behavior without editorialization of what’s important and why.

Summary

If you’ve made it this far, congratulations! Hopefully I’ve armed you with enough knowledge to go test your React code. If you’ve any questions or constructive criticisms, feel free to drop a comment, there are always room for improvement!

--

--