Mocha+Sinon.JSでjQueryのget, ajaxをスタブに置き換えてテストする。

# 概要
jQueryのajaxを使ったコードのユニットテストするために、
今回はSinon.JSのスタブを使って、リモートのAPIを叩かずにテストします。

本記事では、直接jQueryの`$.get`, `$.ajax`を叩いて、
そのcallback内でassertしてますが、
実際のプロジェクトなど、
テスト対象の関数内で`$.get`, `$.ajax`を使ってる場合にも応用できると思います。

Sinon.JSのFake XHRやFake serverを使う方法がネット上にたくさんありますが、
どうもうまくいかなかったのでsinon.stubを利用しました。

20170626 update
その後うまくいく方法が見つかったので別途、投稿しました。
[Mocha+Sinon.JSでjQueryのajaxコールを、XMLHttpRequestを置き換えてテストする。 - Qiita](http://qiita.com/_teruchi/items/86ef461f9d48fb1fce39)


# 環境
macOS Sierra *10.12.5*
Docker for Mac *17.03.1-ce-mac12 (17661)*


# 準備
ローカルの環境を汚さずに済むように、Docker上で環境構築します。

こちらに`Dockerfile`からテストコードまで入ったプロジェクトを上げておきましたので、`git clone`して使ってみてください。

https://github.com/teruchi/mocha-sinon

# 環境構築
git cloneしてdocker buildします

```bash
$ git clone https://github.com/teruchi/mocha-sinon.git
$ cd mocha-sinon
$ ./docker.sh build
```

# テスト実行！
`docker.sh`の中にdockerコンテナ内でmochaを起動するスクリプトを入れておいたので、それを使います。

```bash
$ ./docker.sh mocha
```

どうでしょう？
こんな感じのメッセージが出力されれば成功です。

```
  Test User API
    ✓ get /api/users/100
    ✓ ajax /api/users/100


  2 passing (24ms)
```

# 解説

## Dockerコンテナ内の環境について

### Dockerfile
まずDockerfileの説明をします。

```Dockerfile:Dockerfile
FROM centos:7 【1】

# Install CentOS GPG Key
RUN rpm --import http://vault.centos.org/RPM-GPG-KEY-CentOS-7

# Install EPEL
RUN rpm --import http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7 && \
    yum install -y epel-release && \
    yum update -y && \
    yum clean all

RUN yum install -y --enablerepo=epel \ 【2】
    nodejs npm && \
    yum clean all

# Javascript Test Suite
RUN npm -g install mocha chai sinon jsdom jquery 【3】
ENV NODE_PATH /usr/lib/node_modules 【4】

# Runtime Setting
WORKDIR /opt 【5】
```

CentOS 7上に構築しています。 【1】
`yum`で`nodejs`と`npm`をインストールした上で、 【2】
`npm`で`mocha`、`chai`、`sinon`、`jsdom`、`jquery`をインストールしています。 【3】

`jsdom`を入れないと、ブラウザレスでjQueryを使うことができないので必要でした。

`NODE_PATH`を指定することで、`npm -g`でインストールしたパッケージも読みに行くことができるようです。 【4】

`docker run`した時にテストコードなどが入っているディレクトリで`mocha`コマンドがすぐ使えるように`WORKDIR`をしてしてます。

### docker.sh

`docker build`と`docker run`でmochaを実行するbashスクリプトの説明をします。

```bash:docker.sh
#
# Dockerfile
#
REPO="teruchi/mocha-sinon" 【1】
TAG="latest"
DOCKERFILE="Dockerfile"
SERVICE_NAME="mocha-sinon"


case "$1" in
"build" ) 【2】
    docker build \
      -t ${REPO}:${TAG} \
      -f ${DOCKERFILE} \
      .
    exit
    ;;

"mocha" )　【3】
    docker run \
      --rm \
      --name ${SERVICE_NAME} \
      -v "$(pwd)"/opt:/opt \
      ${REPO}:${TAG} \
      mocha
    exit
    ;;

*)
    echo "usage: docker.sh [build|mocha]"
    ;;
esac
```

私の方で利用しているDocker repositoryのパスです。【1】
ご利用の環境に合わせて変更してください。

`docker build`してます。【2】

dockerのvolume mountを使って、ホストのoptディレクトリを、
コンテナ内の`/opt`にマウントすることで、
テストコードを修正しつつ、scpなど使わずにコンテナ内でテストを即実行します。【3】

### テストコード

最後になってしまいましたが、テストコードです。

```javascript:test.js
// Sinon.JS初期化
var sinon = require('sinon');
var assert = require('assert');

// jQuery初期化
var jsdom = require('jsdom/lib/old-api.js');
var window = jsdom.jsdom().defaultView;
var $ = require('jquery')(window);

/**
 * User APIのテスト
 */
describe('Test User API', function () {

    /**
     * $.getのテスト
     * ※callbackの完了を待つ
     */
    it("get /api/users/100", function (done) {
        // sinon.stab で$.getを置き換え
        sinon.stub($, 'get').callsFake(function(url, success, dataType) {
            success({
                id: 100,
                name: 'teruchi'
            });
        });

        // テスト
        $.get(
            '/api/users/100',
            function (data) {
                // Assertion
                assert.equal(100, data.id);
                assert.equal('teruchi', data.name);

                // スタブから元に戻す
                $.get.restore();

                // callback完了をmochaに通知
                done();
            },
            'json'
        );
    });

    /**
     * $.ajaxのテスト
     * ※callbackの完了を待つ
     */
    it("ajax /api/users/100", function (done) {
        // sinon.stab で$.ajaxのreturnのdoneを置き換え
        sinon.stub($, 'ajax').returns({
            done : function(callback) {
                callback({
                    id: 100,
                    name: 'teruchi'
                });
            }
        });

        // テスト
        $.ajax({
            method: 'GET',
            url: '/api/users/100',
            dataType: 'json'
        })
        .done(function(data) {
            // Assertion
            assert.equal(100, data.id);
            assert.equal('teruchi', data.name);

            // スタブから元に戻す
            $.ajax.restore();

            // callback完了をmochaに通知
            done();
        });
    });
});
```

コメントに書いてある通りですが、ポイントとしては、

- jsdomを使うことでPhantom.jsなどを使わずにjQueryのテストをする
- itのパラメーター（`done`）付きを使うことでcallbackを待つ
- `sinon.stab()`でjQueryのメソッドを置き換える

のあたりかと思います。