Unit Testing React Native Components with Mocha and Enzyme
Tools for testing React Native components have taken huge steps forward during the last six months, and it is now possible to have production quality test setup for any React Native project. This will be your goto-article for unit testing React Native components with Mocha based setup.
As a frontend developer who has some experience building iOS applications in the past I was thrilled to test React Native about a year ago. I had some experience with PhoneGap but the UI performance promise React Native made seemed like an opportunity for all web developers.
Always when working with brand new technology there are pros and cons. React Native definitely helps and eases cross platform application development, but on the other hand fast release cycle with occasional breaking changes slow down the development. Another important thing to consider when selecting the tech stack for a new project is of course the maturity of available tools and community support.
Setup Test Environment
Turns out you can actually use DOM based testing libraries by tricking React Native to return React components instead of native modules. Leland Richardson is huge contributor to React Native community and authors fully mocked and test-friendly version of React Native called (unsurprisingly) React Native Mock. With it you can unit test your React Native components and use any React testing utilities like AirBnB’s Enzyme library.
We enable React Native preset for Babel in .babelrc configuration file:
Next we should export global variables from test utils to reduce code duplication through spec files. We place the file to src/testutils/index.js.
Running the First Test
Now we are ready to write our first spec. Let’s make a basic Hello World component with an input field and a text which changes based on what is written to the input field. Spec for the component might look like something like this:
Now we can write our component:
And see our test pass:
Ignoring Images on React Native Tests
Suppose we would want to add a React Native logo into our component. React Native packager includes a wrapper for requiring image assets which resolves paths to assets in similar way as node modules do. Packager also handles things like selecting the correct size (@2x, @3x etc) based on device screen density and also you can use different images for Android and iOS by using platform specific file extensions.
But when we add an image to our component our tests break completely since our node environment has not the advantage of React Native packager:
To get the tests running again we need to write a require hook for node which intercepts all image requires and returns mocked sources for Image components. For that we’ll create mocha-setup.js and require it before running our tests:
Now the tests pass:
That require hook will make tests to run somewhat slower, but at least now the tests can be run successfully.
Rewiring ES6 Modules
In the past I’ve been a happy user of Rewire module, but with React Native you will probably use ES6 modules instead of CommonJS modules, so Rewire is out of the question. Fortunately Babel has Rewire plugin which is inspired by Rewire and transfers it’s concept to ES6 modules. In our example case we might want to use a text utility function to capitalise user input before updating the component state and showing the text. In order to test it’s usage on our component, we have to enable Babel rewire plugin in test environment, rewire the dependency with Sinon stub and use the utility function on handleTextChange method of ReactNativeTesting component.
Incompatible React Native Modules
Some React Native modules use features which might not be supported on older Node versions. Most of the problems are avoided when using Node v6+, but certain features such as ES6 modules are not yet supported in Node. When we add a module which uses ES6 import statements, such as React Native Share‚ to our component, tests will break as expected:
Solution for this is to use Babel require hooks to define modules you want Babel to transpile. By default Babel ignores node_modules directory. To circumvent it will need Babel ES2015 preset and a require hook to mocha-setup.js file.
Require hook’s regex works so that if filename matches, it will not be compiled. So we are matching everything inside node_modules except react-native-share, so it actually gets compiled and solves our problem:
In case other modules give you a headache, you can add multiple modules to the regex and thus compile the ones you need.
Conclusions and Drawbacks
Building applications with React Native should not differ from any other software development: you should always utilise best practices such as Test Driven Development. But when using the latest and brightest tools you might bump into problems which cannot be found from Google or StackOverflow, especially when you don’t exactly know the problem. As of writing, Jest 14.0 implemented out-of-the-box support for React Native components, so it might be worth to check out. I am more familiar with Mocha based setup, so that is my go-to tool when writing tests.
We at Valuemotive support the idea of sharing our knowledge, so I’ve written an example application for one of our Tech-Together sessions. Please send a pull request if you notice any errors on that code, because contributing actively is the only way to make our common tools better and thus create the most value for our clients.