Working in React Native has been an amazing experience. Coming from React, the workflow has been nearly frictionless. There has, however, been one question lingering: How the hell am I going to test this?
Mocks to the rescue
It turns out that you can unit test React Native component logic in isolation using traditional JS test libraries by tricking React Native into returning regular React components instead of native ones.
Below is my shim for React Native components and methods that allowed me to render them in my test environment:
Mocking React Native with Mocha
First I tried mocking all the things with Jest, mostly because that is what was recommended officially. I was able to prove the concept but there were a couple of drawbacks:
- Jest is unreasonably slow, at least compared to the alternatives
- We typically use a Karma/Mocha/Chai on web React projects at Formidable, so it’d be nice to continue that convention
After bidding Jest farewell, I had to figure out how this was going to work with Mocha. I couldn’t use a Karma/Webpack setup like I would on the web because React Native uses its own packager, so using Webpack would be a redundancy. This means I had to to write a compiler to a) transpile Babel code and b) resolve the React Native module with a mock version.
Mocha takes a — compiler argument that I used to create a custom module loader. I created one below that will transpile .js files and inject the mocked React Native methods into the react-native requires:
Now, any time a module in a test file requires react-native, it will get the shim instead.
Putting it all together
Now I had a mock set up and a custom compiler in place, and I want to see it in action. My first order of business was to install the dependencies required to run the tests. To make things simple, I just ran:
npm i --save-dev mocha chai sinon react react-dom react-addons-test-utils enzyme
To briefly review the role each tool plays in the testing architecture:
- Chai: Assertion library
- Sinon: Stubs, Mocks & Spies
- Enzyme: React testing utility
- React Test Utils: React testing helpers
- React & ReactDOM: Used to mock React Native for testing
Next I had to set up a
mocha.opts file that is used to provide options to Mocha. In a
/test directory, I created the file
mocha.opts and added the following:
Next I created
setup.js in our test directory. The compiler code I wrote earlier went in
compile.js and I put the following code into
setup.js so that I could provide
chai.expect globally, mostly for ergonomics:
Next up, I created
react-native.js in a new mocks directory under
/test/mocks/react-native.js and put the React Native mock code from earlier in there.
The final piece of the puzzle is pointing my test npm script at Mocha with the specified options. I opened
package.json and changed test under scripts to:
"test": "mocha --opts test/mocha.opts src/**/__specs__/*.spec.js"
In addition to pointing to the defined options, I also set the target to tests that use the
*.spec.js convention, located in folders named
Using this convention, I colocate my tests in the folders that contain the components that I’m testing, avoiding lengthy file paths when requiring our components.
Now it’s time to test!
Getting my test on
Say we have a simple React Native component that we want to test to make sure that it renders child nodes in response an items prop:
We want to start by creating a
__specs__ directory in
src/components/. Inside of that directory, we create
our-component.spec.js as our test spec for the above component.
For this test, we will be using shallow rendering, which renders components one level deep and lets us assert against the virtual DOM structure. We will also be using the enzyme library from the fine folks at Airbnb, which is an amazing utility for working with shallow renders.
Let’s start by importing React, enzyme and our component, and creating a describe grouping:
Now that we have our stage set, let’s create some mock prop data, shallow render our component, and use enzyme’s
findWhere method to see if everything is in working order:
Now, all that’s left to do is run
npm test and let the magic happen. We are unit testing React Native components!
I’ve found this approach to be pretty flexible so far, and I am feeling much better about the sturdiness of my React Native code. You may have noticed that in the React Native mock above, I had only mocked out a subset of the functionality. Using this approach may require mocking out additional components and methods depending upon what you are using in your app. I also recommend exploring all the features of enzyme, as it has an elegant API for traversing and querying a ShallowRender objects. Now go write some tests!
Thanks to Ryan Roemer and Alex Lande.