Testing the Bejeezus out of React Native Apps with AVA

Steve Kellock
Red Shift
Published in
8 min readJun 27, 2016

--

This article shows you how to use AVA to test your React Native app. It covers all the 3rd party libraries required to make this happen. Accompanying code is found at https://github.com/skellock/bajeezus.

EDIT: Make sure you’re running Node 6.x.

Why AVA?

Two-way dependency watching.
This is it. This is the reason to use AVA right here. So, mocha and others can detect when you save a change to a test file and re-run it, right? That’s great.

You know what’s mind blowing though?

When you change your app’s code and AVA automatically re-runs tests that might be affected.

Runs tests in parallel.
Each test file is run in a forked AVA process. By taking advantage of the extra CPU cores on your computer, your tests run much much quicker. This is really noticeable when you’re running in watch mode.

Configured in package.json.
You configure AVA by placing additional entries inside your package.json file.

Simple assertions.
You write your test assertions with a non-clever syntax: t.true(someBool), t.deepEqual(a, b), t.is(a, b), and so on.

Pollution-free stack traces.
When something blows up in your tests, you don’t have to wade through frames of AVA’s guts.

Those 3 frames are all our code. Lovely.

Helpful failure messages by default.

Awesome failure messages by default.

Supports pretty much every JavaScript function style.
AVA seems to handle everything you can throw at it. Promises, async, generators, callbacks, and observables.

Babel built in.
This is a nice touch. Unfortunately with React Native testing, we do have to do a little tweaking to ensure it runs the react-native-babel-preset, so it’s not completely hands off.

What Will We Need?

There’s a few libraries that make this process easy. Each brings a little something to the table. All of these pieces together will have you testing your app like it owes you money.

I’ll explain them in the sections below, but the astute reader will be having a WTF moment when they see react-dom above. To which I say,

Embrace your inner rage — for we will unleash it soon.

Note that these are all dev dependencies.

npm i --save-dev ava mockery enzyme react-native-mock react-dom nyc react-addons-test-utils

AVA Configuration

AVA is configured by adding an extra section to your package.json file.

AVA for non-React Native projects works pretty much out of the box with next to no customization required. For React Native, we do have to add a few things.

On line 2, tell AVA that it should pull in the config found in our .babelrc file.

On line 3, We opt-out of AVA’s “test location detection” by telling it where our tests live.

Here we are saying our tests live in recursively under the Tests folder in *.js files, but NOT Tests/Setup.js. That file is a special snowflake that I’ll explain later.

EDIT (2016–06–28): James Talmage points out in the comments below that Tests is not common folder name. Perhaps tests would have been a better choice. If we did that and renamed Setup.js to _Setup.js, we would be be able to omit this line of configuration entirely. Thx James!

Lines 4–9, we tell AVA what scripts to require when we startup our tests.

Here’s what those 4 requires do.

  1. babel-register tells AVA to load any presets & plugins found in our .babelrc file.
  2. babel-polyfill gives us our JavaScript support for generator functions.
  3. react-native-mock/mock gives us a React Native API fake environment.
  4. ./Tests/Setup gives us a chance to mock additional things for our own project.

Babel Configuration

The other thing we need is a .babelrc file in the root of our project. By default, we don’t need one because the React Native packager knows how to bootstrap itself.

The packager has a .babelrc file and it has one entry in it. It uses the preset called react-native. A preset is just a collection of plugins. And plugins make Babel do work.

Our code doesn’t run in the packager though; it runs in AVA when we execute our tests. So, we have to tell AVA to use the same config.

I like to use a .babelrc file for this, because it’s simple. Like me. Both the packager & the tests will use this config file, so if your project needs to add to it, it’s just in one place.

Meet the new .babelrc. Same as the old .babelrc. It lives in the root directory now because we want to play nice with AVA.

Enjoy responsibly.

Testing Straight-up JavaScript

Let’s write some tests shall we?

Let’s start with the non-visual stuff. Just straight up JavaScript testing with AVA.

Here’s the code we would like to test:

Here’s the test we write:

Nothing special right? Right. That’s a good thing.

Testing Redux Reducers

Are you using Dan Abramov’s Redux library?

If you’re building a Redux-based app, you might want to test your reducers. Really there’s nothing different about testing those from the GreeterTest.js above.

Let’s say we have a reducer like this:

Here’s a few tests you can write to ensure everything is sane:

Testing Sagas

If you’re using Yassine Elouafi’s redux-saga, then sagas aren’t too much harder to test. There’s one weird trick you have to do.

Lets say we’re working with this Saga:

Our test might look like:

Inside the test, we:

  1. Grab the saga to test
  2. Create a helper function called step()
  3. Setup any mock data to inject into the saga.
  4. Fire an assertion for each yield statement of the saga.

One thing that throws people for a loop is “when” to inject the mock data into the saga. This feels strange.

Why?

const state = yield select()

Because the yield statement is on the right side of the = sign in your saga. In the above example, the value of `const state` is not actually assigned until you call the next step of the generator.

That’s it. Think of it as lag. Really bad lag. That messes you up. Every damn time. Totally not bitter though.

That’s why the step() function parameter is called mockLastYield. It was either that or deliciousTears or landmineOfLogic.

Creating A Setup.js Script

As far as I understand, React Native has code that needs to live inside an iOS environment (like a device or a sim) or Android environment (like an emulator or device).

Because of this, we need to mock the whole React Native API. And by we, I mean Leland Richardson. What a chore. This guy is a superstar for creating react-native-mock.

So, our project has a Tests/Setup.js file that you saw earlier in the AVA configuration section.

If react-native-mock mocks everything, then why do we need this Setup.js file?

Because there’s things specific to our project we still need to mock. Things like images, the __DEV__ global variable, and sometimes 3rd party components (depending on what you’re using).

What’s left over after react-native-mock does it’s thing, we can use the mockery library.

Here you see us registering mocks for various images we’ve used through-out the app inside require statements. The path to the images depends on how you require them in your components. Use the same thing in these mocks as you do in your app.

I’ve only found 1 library that we need to mock so far. I’m sure there’s more. It’s react-native-router-flux. It uses functions to navigate through your app. It’s really clever. But these functions don’t exist, you need to mock them too.

React Components

Ok, great. So what about testing React Native? That’s the whole point of this article.

And what’s the crap you said before about react-dom?

Well, with Enzyme’s help, we will be actually rendering our components to HTML with react-dom.

POPPYCOCK you say? Well, yes. You’re right. This feels wrong.

Until you do it.

There’s no real need to test UIViews and UITextFields. That’s React Native’s job. What we need to test with our components is: “Are the right components being generated?”

Have a look at this Button component that we want to test:

If you want to test that the right components get spit out, you can use Enzyme’s shallow function to pseudo render.

Have a look at Enzyme’s API for things like find(), children(), name(), omg so much more.

React Component Interactions

But wait! There’s more!

How do you test that onPress function on our component? Can we simulate it somehow? Is this a leading question?

Crazy eh?

Ya, ya, I know it’s all mutable & i’m using let so I’ve just bought a ticket to hell for posting this on the Internet, but how amazing is that?

Bonus: Code Coverage

Hey, so check this out. Which parts of of my saga am I not testing properly? Oh it’s line 5 and 6. Go fix that Steve.

This is nyc. A code coverage tool. It helps you find out what you tests are actually testings, and more importantly, what its not.

Wrap It Up

We just merged these patterns & tools into Ignite, an open-source React Native starter kit. Give it a go when you start your app. It brings everything you see in this article, plus many other best practices, so you can build your app instead of mucking about with tooling.

Also, a thank you goes out to Ken Wheeler who’s article cracked all the hard problems with testing React Native components.

Is this dumb? Am I dumb? Lemme know. I’m @skellock on Twitter, skellock on Github, and work with friendly folks at Infinite Red.

--

--

In the red corner. Weighing in at 9001 pounds. Specializing in React Native and other voodoo…