Jestを使わないでReactをUnitTestする(ProxyRequire編)


## 始めに

この記事は以下の記事の続きです。

[Jestを使わないでReactをUnitTestする](http://qiita.com/uryyyyyyy/items/0161aaacff981de918e5)

目的・ゴールはこちらの記事でご確認ください。

## 試してみる

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の変換を済ませる。

```js:gulpfile.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

```js:util2.js
'use strict';
var AsyncUtil = require('./AsyncUtil.js');

export default {
	returnComplicatedResult() {
		return AsyncUtil.dummyPromise().then(v => v + "util2");
	},
	hello() {
		AsyncUtil.hello();
	}
}

```

```js:mockTest.js
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コンポーネントのテスト

```js:nestComponentTest.js
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に差し替えて、レンダリングされないようにしています。


## 参考資料

[proxyquireでNode.jsのrequireをモックする - ジンジャー研究室](http://jinjor-labo.hatenablog.com/entry/2015/03/20/140617)