mochaでテストを書くときに気をつけたいこと with Sinon.JS

### 確認したバージョン
node v6.9.1
mocha @3.1.2
sinon @1.17.6

## describeとitの処理順番

次の様なテストを書いたとします。
ログはどの様に出力されるでしょうか？

```js
// test.js
describe('doHogeのテスト', () => {

    console.log(0);

    describe('fugaな時', () => {

        console.log(1);

        it('fooはpiyoになる', () => {
            console.log(2);
        });

        console.log(3);

        it('barはpiyoyoになる', () => {
            console.log(4);
        });

        console.log(5);
    });

    console.log(6);

});
```
　
　
..
　
　
　
....
　
　
　
.......
　
　
　:no_good:でっでー

　
before, after などをよく使う方は分かったかと思うのですが、
0,1,2,3,4,5,6 とは出ません。
実際に動かした結果がこちら

```shell-session
$ $(npm bin)/mocha test.js 
0
1
3
5
6


  doHogeのテスト
    fugaな時
2
      ✓ fooはpiyoになる
4
      ✓ barはpiyoyoになる


  2 passing (7ms)
```

itの外側にある0,1,3,5,6が先に出力され、it内にある2,4が後から出力されています。


## なぜ？
before, afterなどの処理のためだと思われます。（mocha内を読もうとしたのですが挫折しましたorz）
mochaはdescribeを実行し、途中で呼ばれた　before, beforeEach, it, afterEach, afterを拾って貯めていき、その後describe毎に、

 - before
    - beforeEach
    - it
    - afterEach
    - beforeEach
    - it
    - afterEach
    - ...（itの分だけ繰り返し）
 - after

という順番で呼び出されます。
itだけ使っている忘れがちですが、こんな理由でitの外と中で処理順番が変わってくるのです。


## 気をつけたいこと with Sinon （本題）
### ここにはまった
前置きが長くなりましたが、ここが伝えたかったところです。結構はまったのでw
私はうっかりこんなテストを書きました。

```js
// test.js
import sinon      from 'sinon';
import { expect } from 'chai';
import * as funcs from './functions'; // テスト対象
import * as utils from './utils';     // テスト対象の内部で呼ばれるやつ

describe('doHogeのテスト', () => {
    describe('fugaな時', () => {

        // 呼ばれる(1)
        // テスト対象のfuncs.doHogeの内部で呼ばれるutils.isHogeをスタブ化
        const stub = sinon.stub(utils, 'isHoge', () => true);

        it('fooはpiyoになる', () => {
            // 呼ばれる(3)
            const actual = funcs.doHoge('foo', 'fuga');
            expect(actual).to.equal('piyo');
        });

        it('barはpiyoyoになる', () => {
            // 呼ばれる(4)
            const actual = funcs.doHoge('bar', 'fuga');
            expect(actual).to.equal('piyoyo');
        });

        // 呼ばれる(2)
        // スタブ解除
        stub.restore();
    });
});
```

前述の通り、実際のテストが実行されるitのコールバック(第二引数)は一度収集され、のちの実行されます。
ですので、テストのためにスタブ化したisHogeは、テストが実行される前にrestoreされてしまっているのです。


### 回避の例：関数化してit内から呼ぶ
before, afetrを使っても良いと思うのですが、ここではスタブ化処理を関数に入れたものを紹介します。

```js
describe('doHogeのテスト', () => {
    describe('fugaな時', () => {

        // スタブ化->テスト対象の実行→スタブ化の解除をまとめた関数
        // これをitの中から呼ぶことで、it毎に確実にスタブ化＋解除が実行される
        const exec = (input) => {
            // テスト対象のfuncs.doHogeの内部で呼ばれるutils.isHogeをスタブ化
            const stub = sinon.stub(utils, 'isHoge', () => true);

            const actual = funcs.doHoge(input, 'fuga');

            // スタブ解除
            stub.restore();

            return actual;
        };

        it('fooはpiyoになる', () => {
            const actual = exec('foo');
            expect(actual).to.equal('piyo');
        });

        it('barはpiyoyoになる', () => {
            const actual = exec('bar');
            expect(actual).to.equal('piyoyo');
        });
    });
});
```



スタブ化したい内容や、テストしたい値の受け取り方次第で書き方も変わるので、実行順番に気をつけて、都度最適化する必要がありますが、１例として参考にしていただければ幸いです。

## まとめ
上から書いた順に実行されないから気をつけて！！！

<br>
<br>
<br>
<br>
## おまけ
説明しやすいようにサンプルコードを書きましたが、本当に書くときはループで書くとよいですよ。

```js
describe('doHogeのテスト', () => {

    // テスト条件をオブジェクトにまとめる
    const dataProvider = {
        'fugaな時、fooはpiyoになる' : {
            data : {
                isHoge : true,
                arg1   : 'foo',
                arg2   : 'fuga',
            },
            expected : 'piyo',
        },
        'fugaな時、barはpiyoyoになる', () => {
            data : {
                isHoge : true,
                arg1   : 'bar',
                arg2   : 'fuga',
            },
            expected : 'piyo',
        },
    };

    // 実行部
    const exec = (data) => {
        // テスト対象のfuncs.doHogeの内部で呼ばれるutils.isHogeをスタブ化
        const stub = sinon.stub(utils, 'isHoge', () => data.isHoge);

        const actual = funcs.doHoge(data.arg1, data.arg2);

        // スタブ解除
        stub.restore();

        return actual;
    };

    // ガンガン回してテスト
    for (let [describe, testCase] of Object.entries(dataProvider)) {
        it(describe, () => {
            const actual = exec(testCase.data);
            expect(actual).to.equal(testCase.expected);
        });
    };
});
```