node.js + ローカルインストールmochaをWindowsで使う

# はじめに

node.js にテストフレームワーク導入する方法って話。
普通にやれば困らずに出来る範囲なんだけど
「普通」に未だ辿りついてない私レベルの方々の参考に成れば！、
っていう話。
実行方法と、実行していく中でハマッた事例を記載。


ネットをザッと検索した印象では、現状（2015～2016）だと
「とりあえずMochaでいんじゃね？」っと感じられたので、
ここでは数あるJavaScriptのテストフレームワーク中で
mochaを採用する。

なお、mochaは、jQuery環境（ブラウザ環境）でも使えるみたい。
でも以下ではコマンドライン前提（node.js）。



# Mochaの実行ファイルのありかと、テストコードの実行方法

`npm install mocha` でローカルインストールする。
多くの解説サイトでは、この直後に「`mocha`でテストを実行可能」って
書いてあるけど、それはLinux環境前提な。

Windows環境の場合は、**パスが通っていないのにで、NG**。

実行ファイルが「node_modules\.bin\mocha」辺りにあるので、
「node_modules\.bin\ 」をNODE_PATH環境変数に設定するか、もしくは、
以下を記述したバッチファイル `mohca.bat` を test フォルダの上位に置いておく。

`@node_modules\.bin\mocha   %1 %2 %3 %4 %5 %6 %7 %8 %9`

※配置は以下を前提。
> mocha.bat
> ＋node_modules
> ＋src
> ｜　＋― 被テストコード.js
> ｜
> ＋test
> 　　＋― テストコード.js


これで、コマンドプロンプト上から
`mocha` 
で実行可能になる。
※もちろんテストコード.js」が無ければ何も走らない。

テストコードは、以下のようにして書くが、詳細は割愛。

```js:test.js
describe( "テストグループ。ファイル名とか", function(){
	describe( "メソッド名とか",function(){
		it( "テストの内容",function(done){
		/* assert等を用いてテストコードを書く */
		}
	}
}
```

`mocha  test\テストドライバー.js` でファイル単位で実施可能。
指定しない場合は、testフォルダの中全部が実行される。

※以降のサンプルコードと環境は、`chai` と `sinon` の利用を前提。
`npm chai sinon` でインストールすること。





# 【注意】Mocha＋非同期がエラーする場合の対処方法。
正確には、
「アサーション失敗（NG）ったときに、
　エラー内容の表示ではなく**タイムアウトエラー**扱いされる」
不具合の対処方法。

「期待値はhogeですが、実行結果はpiyoでした。失敗」
が欲しいのに、
「テスト実行は**timeoutしました**」って言われて、
それじゃねーんだよ、欲しいのは！、な例。

これは、
「Promise() ベースの非同期実装している場合は、
　done()を呼び出すのではなく、
　Promise.then()のインスタンスを返却する」
ことで回避できるみたい。

以下、サンプルコード。
先ずはテストされるコード。1秒後にcallback()を呼ぶ。

```js:promise_trial.js
var p = function( argv, callback ){
	var promise = new Promise( function(resolve, reject){
		setTimeout( ()=>{ 
			resolve(); 
		}, 1000 );
	});

	return promise.then(function(){
		console.log( argv );
		callback( argv );
	})

}
exports.p = p;
```

続いて、テストコード。
最初の2つは、done();で実装。
正常系は問題ないが、異状系が「**タイムアウト**」って言われる。
これを修正したのが、後の2つ。
異状系でも、ちゃんと「～がNG」と言ってくれる。

```js:promise_trial_test.js
const chai = require("chai");
const assert = chai.assert;
const expect = chai.expect;
const sinon = require("sinon");
const target = require("../src/promise_trial.js");

describe( "Promiseでの非同期実装のテスト方法", function(){
	describe( "正常系はOK：done()利用",function(){
		it( "done()で完了通知",function(done){
			target.p("hoge", function(argv){
				expect( argv, "正常系は問題ない" ).to.equal("hoge");
				done();
			});
		});
	});

	describe( "異常系はタイムアウト扱いされる：Done()利用",function(){
		it( "done()で完了通知",function(done){
			target.p("piyo", function(argv){
				expect( argv, "異常系を拾えない" ).to.equal("hoge");
				done();
			});
		});
	});

	describe( "Rromiseをreturnすることで、エラーも正しく拾える：正常系",function(){
		it( "done()ではなくPromiseで完了通知",function(){
			var stub = { "callback" : ( argv )=>{ /* 処理無し */ } };
			var spyCallback = sinon.spy( stub, "callback" );

			var promise = target.p("hoge", stub.callback);
			return promise.then( function(){
				expect( spyCallback.getCall(0).args[0]).to.equal("hoge");
			});
		});
	});

	describe( "Rromiseをreturnすることで、エラーも正しく拾える：異常系",function(){
		it( "done()ではなくPromiseで完了通知",function(){
			var stub = { "callback" : ( argv )=>{ /* 処理無し */ } };
			var spyCallback = sinon.spy( stub, "callback" );

			var promise = target.p("piyo", stub.callback);
			return promise.then( function(){
				expect( spyCallback.getCall(0).args[0]).to.equal("hoge");
			});
		});
	});

});
// アロー関数が混在しているのはスルーでよろ。
```

# chaiとsinon に関する備忘録。

## chai のdeepメソッドを用いてオブジェクトの中身を検証
```
var func = function(){ return { /* オブジェクトを返す */ }; }

expect( func(), "オブジェクトの中身で検証する" )
.to.depp.equal( { /* 期待値をザッと書く */ } );
```

## スパイ、スタブ、モック、の利用用途との違い。

spy()
　呼び出し先のメソッドが既に存在する（機能する）時に使う。
　呼び出し先のメソッドはそのまま実行。
　何回呼び出されたか？　何の引数で呼び出された？等をチェックする。

stub()
　呼び出し先のメソッドが未だ無い、時に使う。
　呼び出し先のメソッドは実行しない。

mock()
　Stub()の機能にプラスして、
　何回呼び出された？　何の引数で呼び出された？等をチェックする
　stub() + spy() のイメージ。

(ちょっと違うかもしれないが…)

※なお、ここで「呼び出し先のメソッドを実行しない」などの
　Overrite動作が出来るのは、テストコード内のみ。
　非テストコードのAファイル内で参照されている変数は、
　例え`exports`されていたとしても
　弄ることはできないっポイ（[こちら](http://qiita.com/xingyanhuan/items/531a0b9a4cdc30e7d087)で検証してみた）。



# 参考にしたページ
## mochaとchaiとsionn関連
[Expect / Should - Chai]
http://chaijs.com/api/bdd/

[npm と Node.js 上で require を使ったモジュール読み込みの仕組みについてメモ | phiary]
http://phiary.me/npm-node-js-require-module-memo/

[javascriptのテストのはなし：Sinon.JS(その1) ｜ Developers.IO]
http://dev.classmethod.jp/etc/javascript_testing_framework_sinonjs-1/

[jsでTDD！MochaとChaiとsinon.js入門 - lxyuma BLOG]
http://lxyuma.hatenablog.com/entry/2013/12/15/211637

[mochaとchaiの最も基本的な使い方 - 30歳からのプログラミング]
http://numb86-tech.hatenablog.com/entry/2016/06/08/155834
⇒「ブラウザでのmocha実行方法」も載ってた。


## mocha＋Promiseな非同期環境でエラー拾えない。
[Promise な関数を mocha でテストする - ぶるーすくりーん]
http://mid0111.hatenablog.com/entry/2015/08/23/214647
＞「expect は hoge なんだけど実際は fuga だったよ」って言ってほしいのにタイムアウトエラーになる。

[Bluebirdを利用したコードをMochaでテストする - Qiita]
http://qiita.com/ysm001/items/c1070a363857a9118c89
＞assertを呼び出しエラーが起きた時点でcatch handlerに飛ぼうとするため、後続のdoneが実行されずにtimeoutとなる

[Mocha - the fun, simple, flexible JavaScript test framework]
https://mochajs.org/
＞WORKING WITH PROMISES