Learning to test React Native with Jest — part 2

The need for a good spy

Jason Gaare
React Native Training
5 min readJun 1, 2017

--

This article assumes you are familiar with Part 1.

Beyond Snapshots

Previously, we looked at creating Jest snapshots of the rendered output from our React Native components. When we run our test of CameraSettings and glance at the coverage report, here’s what we see.

Testing with snapshots alone doesn’t check the functionality of our rendered components.

The red-highlighted lines aren’t covered by our snapshot. To test these onValueChange events with Jest, we’ll have to simulate some user interaction. Luckily for us, Enzyme makes it a breeze to simulate user touches.

How to simulate a touch

To test our switches, we’ll utilize the dive, find, and simulate functions of our shallow render.

Once we have our rendered output, we call .find(‘ComponentName’) to get all direct children of the type ComponentName. Then, we iterate through them using a for-each loop, calling .simulate(‘Action’) on each, where ‘Action’ is the name of the touch event handler prop on our component with the word ‘on’ chopped off. Check it out below:

Couldn’t be more straightforward than that. I love how readable this test is.

Let’s check in on our coverage report…

No more red highlights!

After running our test, all the lines are covered! Done and done, right? Not quite…

We might have made our coverage report look more complete, but we really didn’t test is whether or not our component executes the functionality we expect.

The need for a good spy

To assert the correct functionality of our switch, we’ll need what’s called a spy. With spies, our goal is to determine that when a user interacts with our app (like toggling a switch), the appropriate functions are called.

There are plenty of libraries that provide the spy functionality. In our project, we utilize the Sinon.JS library. You can add it to your project by running npm i --save-dev sinon.

So, what exactly does that switch do?

Here’s a snippet from enableCameraRoll, the function called by our first switch’s onValueChange.

enableCameraRoll = (value) => {
// send the value to our native module
NativeModules.PhotoActions.saveToPhone(value);
...
}

When the user toggles the switch, we pass its value to the method saveToPhone. We need to assert our saveToPhone method is called when the user toggles our Switch.

But of course the method will get called, it’s right there in the function! Yes, while that’s true, the point of confirming this is so whenever we make changes to this file or refactor things a bit, we always know the functionality of our switch hasn’t changed.

Adding the Spy

With Sinon.JS, we can easily spy on everything that relates to a specific function in our code. The setup couldn’t be simpler: const ourSpy = sinon.spy(object, ‘method’).

For our example, we need to spy on the saveToPhone method in our NativeModule PhotoActions. We’ll create our spy like so:

const saveToPhoneSpy = 
sinon.spy(NativeModules.PhotoActions, 'saveToPhone');

What to expect (…literally)

In part 1, we didn’t talk much about the word expect in Jest (even though we used it), but it’s a keyword designed to be readable out-loud, much like it.

In our test, the switch is toggled a single time so we expect saveToPhone will be called once. We write our test’s expect statement to read just that:

expect(saveToPhoneSpy.calledOnce).toBe(true);

Here’s what we’ve added so far (note: new imports on lines 5 and 6)

Running our test (spoiler alert: it fails.)

Let’s take this baby for a whirl. Here’s our test output:

Well this is less than a good feeling.

Shoot. Looks like we still have some work to do.

Understanding the error

Looking at the stack trace, on the second line we see the name of our test file, and at the end, :28:36. This is the line number of our error, followed by the column number. Let’s look at line 28 of our test:

28  const saveToPhoneSpy = 
sinon.spy(NativeModules.PhotoActions, 'saveToPhone');

We’re getting an error because our spy isn’t able to find the saveToPhone method. How can we fix this?

We will, we will Mock you (my favorite Queen song)

Recall that the whole idea behind a spy is that we don’t actually care what the method our function calls actually does — we just want to know the method in indeed called. This is also the idea behind mocks.

Mocking NativeModules.PhotoActions

To resolve our error in Jest, we’ll need to mock our saveToPhone method. That way when Jest begins to search for NativeModules, we can tell it to forget looking elsewhere, and to use our mock instead.

All Jest needs to know is that saveToPhone is a function, so we’ll return jest.fn() for the saveToPhone method in our mock:

jest.mock('NativeModules', () => {
return {
PhotoActions: {
saveToPhone: jest.fn(),
},
};
});

Note: Our example here is about as plain as you can get, but in actuality the Jest mock function itself holds a lot of power and you can do a many awesome things with it to mock your method functionalities. (Here’s the docs from Jest on mock functions).

Putting it all together

Alright, here’s our completed test (polished up a bit, and including our snapshots from last time):

And when we run it…

Green is good.

Success! But there’s still more to do…

In our actual component, this.enableCameraRoll has functionality beyond simply calling saveToPhone. We’ll need to implement spies for those other methods as well. In the process, we might encounter more mocks we need to write. After that, we’ll need to think about what we actually expect to happen from those spies. Then, what about the edge cases? Better make sure those are covered, too…

Writing tests takes time

All this just to test a single toggle switch? It is a lot. Writing a truly complete test can be a tedious, time-consuming process. But it’s worth it. The time it takes to write a test should be seen as a valuable investment in the integrity of your project.

Our motivation for taking the time to write tests should be to prevent bugs and unintended changes in functionality from making it into production.

Hopefully this article will help you save a little time getting started on testing some functionality in your React Native app. For what it’s worth, here’s an interesting article on the cost of fixing a bug in development vs. production.

Topics yet to come

Looking ahead, here are some topics for subsequent posts:

  • Running jest command line without npm
  • Understanding test coverage reports
  • Mocking responses from API requests for testing
  • Creating a setup file with global mocks
  • Best practices when mocking libraries

More in this series:

Part 1Part 3Part 4

Check out our React Native application here. Good luck and happy testing!

--

--

Jason Gaare
React Native Training

Mobile developer at @CompanyCam. Husband, father, follower of Christ.