始めに
この記事は以下の記事の続きです。
目的・ゴールはこちらの記事でご確認ください。
試してみる
https://github.com/uryyyyyyy/React_mocha_boilerplate
をダウンロードして、
# node -v => 0.12
npm install -g gulp
npm install
をしたあとに、
gulp test:testUtil --path src/reactComponents/_tests_/nestComponentTest.js
あたりを試してみてください。
(Node.js v0.10.Xで動かすときは、jsdomの設定によってはエラーになります。適宜jsdomをいじってください。)
仕組みの概要
- mochaベースにする。
- テスト対象がrequireしているものを
proxyrequire
でmockする。 - テストの前にjsx->jsの変換を済ませてしまう。(babelを使う。)
- 既存のソースコード(jsx, babel書式のコード)に一切手を加えない。
ソースコードは以下に置いています。
https://github.com/uryyyyyyy/React_mocha_boilerplate
gulpfileにwebpackでのbuildも記述しているので、ちゃんと普通のプロジェクトとして動くことも確認できます。
具体的な仕組み
事前にbabel→jsの変換を済ませる。
/* prev codes*/
gulp.task('test:compile', ['test:clean'], function () {
var babel = require("gulp-babel");
return gulp.src("src/**/*.js")
.pipe(babel())
.pipe(gulp.dest("./testSandbox/src"));
});
gulp.task('test:testUtil', ['test:compile'], function () {
var argv = require('minimist')(process.argv.slice(2));
var mocha = require('gulp-mocha');
console.dir(argv);
return gulp.src('./testSandbox/' + argv.path)
.pipe(mocha({}));
});
test:compile
タスクで、src以下の全ファイルにbabel変換をかけています。
これによって、ソースコード・テストコードをbabelで書いても普通のjsファイルとしてテストができます
(ソースマップは考えてないので、エラーの行番号などはズレますが。。)
「毎回全ファイルを変換するのは重たいのでは?」という懸念がありますが、
今のところ気にならないと思っています。(サンプルプロジェクトの規模なら1秒以内くらい。)
規模が大きくなってきたら、gulpのタスクを分割して対応する方法もあります。
proxyrequire
'use strict';
var AsyncUtil = require('./AsyncUtil.js');
export default {
returnComplicatedResult() {
return AsyncUtil.dummyPromise().then(v => v + "util2");
},
hello() {
AsyncUtil.hello();
}
}
var assert = require('assert');
var proxyquire = require('proxyquire');
describe("#mockTest", function () {
var mock = {
hello() {console.log("hello mock");}
};
var util2 = proxyquire("../util2.js",
{ './AsyncUtil.js': mock });
it("mock hello", function () {
util2.hello();
});
});
proxyrequire経由でutil2.jsを読み込ませると、util2.jsがrequireしているAsyncUtil.jsがMockされます。その結果、util2.hello()
メソッドはmockオブジェクトのhello() {console.log("hello mock");}
を呼ぶことになります。
reactコンポーネントのテスト
var assert = require('assert');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var proxyquire = require('proxyquire');
var jsdom = require("jsdom");
global.document = jsdom.jsdom("<!doctype html><html><body></body></html>");
global.window = document.defaultView;
global.navigator = window.navigator;
describe("#react nestComponent test", function () {
var mock = require("react").createClass({render() {return null;}});
var NestComponent = proxyquire("../NestComponent.js", {'./UntouchableOne.js': mock});
it("render NestComponent, but don't render untouchableOne", function () {
var nestComponent = TestUtils.renderIntoDocument(
<NestComponent />
);
assert.equal(nestComponent.calc(), 3);
});
});
jestを使わなくてもそれなりにシンプルに書けますね。
ポイントとしては、
・jsdomを用意してあげる必要がある。
reactのコンポーネントはDOM上でインスタンス生成しなきゃいけないのでjsdomが必要です。
・jsxで書くなら事前に変換を行う。
テストもjsxで書こうと思うと、当然テスト実行前に変換が必要です。(今回はgulpタスクに組み込んでいます。)
・mockを挟むことで、子コンポーネント以下がレンダリングされるのを防ぐ。
普通にコンポーネントをrequireして配置すると、そのコンポーネントの子コンポーネント全てが再帰的にレンダリングされてしまいます。すると、意図しないところで副作用が生じたり時間がかかったりして、テストがやりにくくなります。
今回はproxyrequireを使うことで、子コンポーネント(UntouchableOne.js)をmockに差し替えて、レンダリングされないようにしています。