Skip to content

Instantly share code, notes, and snippets.

@jmreidy
Last active January 9, 2018 11:49
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jmreidy/4145809229195441d4d4 to your computer and use it in GitHub Desktop.
Save jmreidy/4145809229195441d4d4 to your computer and use it in GitHub Desktop.
Unit test React Native with Mocha
--compilers js:./test/support/compiler
--require ./test/support/init
/* eslint-env node, mocha */
import NoteEntryScreen from '../src/containers/screens/NoteEntryScreen';
import common from './support/common';
const {createRenderer, React, expect, MockComponents} = common;
function setup() {
const props = {};
const renderer = createRenderer();
renderer.render(<NoteEntryScreen {...props} />);
const output = renderer.getRenderOutput();
return {
props,
output,
renderer,
};
}
describe('NoteEntryScreen', () => {
it('should render a scrollview', () => {
const { output } = setup();
expect(output.type.displayName).to.equal(MockComponents.ScrollView.displayName);
});
});
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import React from 'react';
import ReactTestUtils from 'react-addons-test-utils';
import {MockComponents} from './mocks/react-native';
chai.use(chaiAsPromised);
export default {
expect: chai.expect,
createRenderer: ReactTestUtils.createRenderer,
React,
MockComponents,
};
var fs = require('fs');
var path = require('path');
var babel = require('babel-core');
var origJs = require.extensions['.js'];
require.extensions['.js'] = function (module, fileName) {
var output;
if (fileName === '/app/node_modules/react-native/Libraries/react-native/react-native.js') {
fileName = path.resolve('./test/support/mocks/react-native.js');
}
if (fileName.indexOf('node_modules/') >= 0) {
return (origJs || require.extensions['.js'])(module, fileName);
}
var src = fs.readFileSync(fileName, 'utf8');
output = babel.transform(src, {
filename: fileName,
sourceFileName: fileName,
//keep below in sync with babelrc
"retainLines": true,
"compact": true,
"comments": false,
"plugins": [
"syntax-async-functions",
"syntax-class-properties",
"syntax-trailing-function-commas",
"transform-es2015-arrow-functions",
"transform-es2015-block-scoping",
"transform-es2015-classes",
"transform-es2015-computed-properties",
"transform-es2015-constants",
"transform-es2015-destructuring",
["transform-es2015-modules-commonjs", {"strict": false, "allowTopLevelThis": true}],
"transform-es2015-parameters",
"transform-es2015-shorthand-properties",
"transform-es2015-spread",
"transform-es2015-template-literals",
"transform-class-properties",
"transform-flow-strip-types",
"transform-object-assign",
"transform-object-rest-spread",
"transform-react-display-name",
"transform-react-jsx",
"transform-regenerator"
],
"sourceMaps": false
}).code;
return module._compile(output, fileName);
};
import React from 'react';
function mockComponent(type) {
const Component = React.createClass({
displayName: type,
propTypes: { children: React.PropTypes.node },
render() { return React.createElement(React.DOM.div, this.props, this.props.children); },
});
return Component;
}
const componentsToMock = [
'View',
'Text',
'Component',
'ScrollView',
'TextInput',
];
export const MockComponents = componentsToMock.reduce((agg, type) => {
agg[type] = mockComponent(type);
return agg;
}, {});
export default {
...React,
...MockComponents,
StyleSheet: {
create: (ss) => ss,
},
PropTypes: React.PropTypes,
};
@jmreidy
Copy link
Author

jmreidy commented Nov 12, 2015

Getting React Native components unit tested with Mocha.

In a normal setup, requiring a component file in a unit test will import React from react-native, which will start throwing errors immediately. To work around these errors, we have to swap out react-native wholesale with vanilla React, and then mock out the react-native components. (Which is fine, since in a unit test we should only care about the system under test anyway).

We can easily witch out react-native without anything like proxyquire by using a custom compiler, which just switches out the import.

Then it's just normal unit testing with mocha!

Copy link

ghost commented Nov 26, 2015

@jmreidy any chance you've run across any RN seed applications employing this technique? I'm wary about using Jest to unit test my RN applications, and would like to go with Mocha if possible if the library substitution method pans out.

@IanVS
Copy link

IanVS commented Dec 1, 2015

Thanks for putting this together, @jmreidy. I'm giving it a shot now. Do you have anything special in your ./test/support/init?

@Andrewpk
Copy link

Andrewpk commented Dec 5, 2015

This is awesome @jmreidy. Are there any additional steps to getting this to work that may have been left out?
Currently I'm having trouble getting the tests to run due to RN requires:

Error: Cannot find module 'ActivityIndicatorIOS'

@jrichardlai
Copy link

@Andrewpk you want to make sure the path is correct in compiler.js I replaced it with fileName.match('/node_modules/react-native/Libraries/react-native/react-native.js')

@miquelbeltran
Copy link

Hi @jmreidy, any change you can upload a full repo with the working example? I am following your code examples but I am stuck with an issue I don't know how to solve when running mocha.

I'll leave here the error stack trace just in case you have any idea. Many thanks in advance!

 mocha
/home/miquel/dev/github/react-app-test-2/AppTest2/app/scroller-item.js:1
(function (exports, require, module, __filename, __dirname) { 'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if("value" in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor;};}();var _reactNative=require('react-native');var _reactNative2=_interopRequireDefault(_reactNative);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class 

TypeError: Super expression must either be null or a function, not undefined
    at _inherits (/home/miquel/dev/github/react-app-test-2/AppTest2/app/scroller-item.js:1:1287)
    at /home/miquel/dev/github/react-app-test-2/AppTest2/app/scroller-item.js:11:35
    at Object.<anonymous> (/home/miquel/dev/github/react-app-test-2/AppTest2/app/scroller-item.js:21:75)
    at Module._compile (module.js:413:34)
    at Object.require.extensions..js (/home/miquel/dev/github/react-app-test-2/AppTest2/test/support/compiler.js:51:17)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Module.require (module.js:367:17)
    at require (internal/module.js:16:19)
    at Object.<anonymous> (/home/miquel/dev/github/react-app-test-2/AppTest2/test/scroller.test.js:1:81)
    at Module._compile (module.js:413:34)
    at Object.require.extensions..js (/home/miquel/dev/github/react-app-test-2/AppTest2/test/support/compiler.js:51:17)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Module.require (module.js:367:17)
    at require (internal/module.js:16:19)
    at /usr/lib/node_modules/mocha/lib/mocha.js:219:27
    at Array.forEach (native)
    at Mocha.loadFiles (/usr/lib/node_modules/mocha/lib/mocha.js:216:14)
    at Mocha.run (/usr/lib/node_modules/mocha/lib/mocha.js:468:10)
    at Object.<anonymous> (/usr/lib/node_modules/mocha/bin/_mocha:403:18)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:141:18)
    at node.js:933:3

@larubbio
Copy link

@miquelbeltran I'm running into the same issue you are. Also is there anyway to use the existing .bablerc instead of duplicating it in code?

@larubbio
Copy link

@miquelbeltran I had to modify the mock to get passed the TypeError

var ReactNative = {
  ...React,
  ...MockComponents,
  StyleSheet: {
    create: (ss) => ss,
  },
  PropTypes: React.PropTypes,
};

module.exports = ReactNative;

I think the error had to do with Component not being defined so when babel tried to transpile a module that extends it you get the error. We might be on a new version of some package that requires the different export syntax.

http://stackoverflow.com/questions/30116430/reactjs-giving-error-uncaught-typeerror-super-expression-must-either-be-null-or

I've also had to add mocks for at least one other module (react-native-router-flux) to get my tests to "run".

@sstur
Copy link

sstur commented Mar 2, 2016

I also got this working with some effort and modifications to the above files. Will post my solution...

@cornedor
Copy link

You could use the react-native preset so you won't have to keep the transforms in sync
https://gist.github.com/cornedor/37ab65a3a505797b01c2e78156d5cf12/revisions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment