Use Mocha, proxy calls to react-native or expo through proxyquire (write stubs), and use @babel/register and @babel/polyfill in mocha.opts so Node understand ECMAScript modules.
I developed an app in React Native, using Expo, which stores data with expo-sqlite and AsyncStorage, and has quit a complex core logic, reading and writing data. This article presents how I can test this logic alone, with classic JS tools (mocha). I do not want to test the interface nor React components. Just JS logic like in a Node application.
The file we want to test (the core logic), dataManager.js, is the following:
With db.js being a simple wrapper around expo-sqlite:
You can see that end dependencies are AsyncStorage from react-native and sqlite from expo-sqlite. Using Mocha, our specs file looks like following (#TDD 🤓):
ECMAScript modules in Node
However, if you try to execute mocha, you will get the following error:
This is normal: we are now in a NodeJS environment, and we try to use React Native files, which use ECMAScript modules syntax (import … from …), whereas (for now*), NodeJS only recognizes commonjs module syntax (const module = require('module')). To solve this, we use Babel to transpile code on-the-fly.
That way, Mocha will call Babel before executing tests, so Node understands the code.
Stub React Native and Expo: proxyquire ❤️
First, write a stub of the methods from react-native or expo that you use. For example, to emulate AsyncStorage from react-native, we can use the following basic code:
In tests, we want our code to use this method instead of the original one from react-native. To do this, we can use proxyquire to intercept and proxy calls to a node module, like react-native or expo-sqlite (or any other).
npm install --save-dev proxyquire
In our spec files, we create a proxied version of dataManager:
And then, simply use this object instead of the original dataManager in your specs file:
For the sqlite part, we will use sqlite3 node module: npm install --save-dev sqlite3. Then, in react-spec-helpers.js file, write a function which follows the same interface as our function execSql from db.js, but using sqlite3 module instead of expo-sqlite:
Again, use proxyquire to leverage this implementation when dataManager.js wants to use db.js:
That way, we have a real SQLite database and can write realistic tests.
We saw that it's possible to use classic JS tools like Mocha to unit test JS logic in a React Native app, like you would do for a NodeJS application, thanks to proxyquire and the right Babel options in mocha. Plus it's fast, no need to compile nor launch the React Native app.