Learning to test React Native with Jest — part 3

Combining spies and snapshots

Jason Gaare
React Native Training
4 min readJun 12, 2017

--

Spies and snapshots are powerful tools on their own. When we put them together, they make Jest an extremely powerful and easy tool to use for testing React Native components.

Quick run down of the FilterBar component we’ll be testing

In our app, users can filter photos in a list by who uploaded each photo. Here’s a sample of our filter bar in action: we’ve selected to view only the photos uploaded by two users.

Show me photos uploaded by Academy Award winners only, please

Most of the layout for the bar is handled by a different component, but our component itself holds the function removeFilter, which is called when the user touches the white ‘x’ button on an individual filter. We want to test that function to make sure it behaves as expected.

Here’s the function:

Testing functions within components

We can easily test functions within our component utilizing the tools we already know. First, we need to setup our shallow render and spies.

What’s going on here:

  1. We setup a generic spy called onFiltersChangeSpy. This function is called by removeFilters.
  2. We create our shallow render, passing the spy and also some sample filters (which we are about to remove!)

Invoking our removeFilters() function

To access functions within a component, we need to have an instance of that component. We can get that instance directly from our shallow render using .instance() (straightforward, I know). Then, just call the function!

render.instance().removeFilter({ id: 1 });
render.instance().removeFilter({ id: 2 });

Did it work?

The success of our function requires that nextFilters is correctly calculated and passed to this.props.onFiltersChange. Therefore, we want to create assertions surrounding the arguments passed to our spy. We can easily access the arguments passed to our spy using onFiltersChangeSpy.args

Manually checking equivalence (the tedious way)

Our initial filters were { ids: [1, 2] }, so we could simply create objects that represent what nextFilters should be after each time we call removeFilters. Those statements would then be:

expect(onFiltersChangeSpy.args[0][0]).toMatchObject({ ids: [2] });
expect(onFiltersChangeSpy.args[1][0]).toMatchObject({ ids: [] });

This method works perfectly fine, but as we saw before…

Using a snapshot is so much simpler!

Why waste our time painstakingly writing all our scenarios into individual expect statements, when we can check them all using a snapshot? Check it out:

expect(onFiltersChangeSpy.args).toMatchSnapshot();

Inspecting our initial snapshot (don’t skip this!!)

The snapshot makes it a breeze to confirm that whenever we run our test in the future, the correct nextFilters will be sent again to onFiltersChange. However, we need to inspect our initial snapshot to ensure it holds the information we expect.

Here’s our snapshot:

You might notice our two cases we tested for: after each removeFilter call, our filters are [2] and then [], respectively. Looks like our function works! You can checkout our completed test here.

Using snapshots to test redux actions & reducers

If you utilize Redux, a lot of the functionality you’ll be functional testing will involve dispatch. These actions can quickly grow in complexity, but with snapshots testing them is still a breeze.

A great benefit of snapshots is they can easily handle complex objects and test equivalence efficiently.

Most of my learning about this topic came from this awesome article. I’d highly recommend you read it, and because I’m assuming that you will, I’m not going to get too in-depth here. Here are the basics:

  1. To test actions, we create a redux-mock-store and dispatch them.
  2. To test reducers, we import the reducer and pass a current state and action object into it.

Example test utilizing redux and snapshots

Here’s our test for (1) the actions dispatched by authenticating a user at login, and (2) how the LOGOUT action is handled by our user reducer.

The resulting snapshots:

(1) Our authentication snapshot shows that four actions are dispatched upon authentication. For brevity, the full account and user objects within the actions are not displayed here, but in our actual snapshot they are objects with dozens of key-value pairs. The snapshot ensures those objects will match every time we run our test.

(2) Our reducer snapshot confirms that our initial state for the user reducer is returned upon LOGOUT.

I did leave one thing out…

I would be remiss not to mention that our authenticateUser function above actually makes a call to our API to authenticate the information and then dispatches all those actions once the user’s information is returned.

How did I mock that API call for our test? That and more to come soon…

More in this series:

Part 1Part 2Part 4

Bonus: Check out our React Native app here

--

--

Jason Gaare
React Native Training

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