JavaScriptのテスト環境構築(Mocha + power-assert + Karma + babel + webpack)


この記事は、初めてJavaScriptのテスト環境を作ってみたおじさんによる、これからJavaScriptのテストを書いていきたいけど、登場人物が多すぎてなにやらめんどくさそうと思っている方に向けた記事兼備忘録です。

**初めてのJavaScriptテスト環境構築で一応公式ドキュメントに目を通したけど英語の意味をちゃんと読み取れておらず、ツールやライブラリの理解や使い方が間違っている場合がありますのでアドバイスいただけると幸いです。**


各テストツールやライブラリの紹介
=================================

JavaScriptのテスト環境を構築するときの一つ目の壁がツールやライブラリがたくさん出てきて、どれを使っていいかわからない/それぞれの役割がわからないことだと思う。

なので、まずはJavaScriptのテスト環境についてググったときに出てくる最近使われてそうな各ツールやライブラリの役割をすごく雑に紹介します。


テストフレームワーク
--------------------

以下で紹介するフレームワークはどれも見た目がRSpecっぽい。触ったことがあれば雰囲気は簡単につかめるはず。

### mocha

[Mocha - the fun, simple, flexible JavaScript test framework](https://mochajs.org/)

* クライアントサイドとサーバサイドの両方で動作する
* アサーションやmock/stub機能は持っていないので他のライブラリと組わせて使う

RSpecっぽいBDDスタイルのほかにも, TDD, Exports, QUnit, Require-styleの中から好きなインターフェイスを選択して書ける。テスト結果の出力もかなり柔軟に指定できる感じで猫が走ったりする。ドキュメントを読む限りだと非同期処理のテストも簡単に書けそう。

```js
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      assert.equal(-1, [1,2,3].indexOf(5)); // アサーションは自由に選択化
      assert.equal(-1, [1,2,3].indexOf(2));
    });
  });
```

### Jasmine

[Jasmine: Behavior-Driven JavaScript](http://jasmine.github.io/)

* サーバサイドはjasmine-nodeを使う
* mockやアサーションのライブラリが備わっている


```js
describe("A suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});
```


### jest

[Jest | Painless JavaScript Unit Testing](https://facebook.github.io/jest/)

* テストフレームワーク
* Facebookが作ったJasmineの拡張テストフレームワーク
* mockやアサーションのライブラリが備わっている

Jasmineとの違いとして読み込むコードは全てMockとして扱われ、テスト対象コードをMockから明示的に除外して使うのが特徴。

```js
jest.dontMock('../sum');

describe('sum', function() {
 it('adds 1 + 2 to equal 3', function() {
   var sum = require('../sum');
   expect(sum(1, 2)).toBe(3);
 });
});
```

アサーション
------------

アサーションライブラリを自由に選択できるmochaで使う

### power-assert

[power-assert-js/power-assert: Power Assert in JavaScript. Provides descriptive assertion messages through standard assert interface. No API is the best API.](https://github.com/power-assert-js/power-assert)

アサーションライブラリとして説明されること多いが、APIはNode.jsの単純な[Assert API](https://nodejs.org/api/assert.html)。power-assertはテストが失敗したときの出力結果を拡張したもの。テストが失敗したときの情報をたっぷり出力してくれるため、他ライブラリのようにたくさんのアサーションAPIを使い分ける必要がない。

以下のスライドと記事が詳しい

[5minで分かるpower-assert](http://azu.github.io/slide/sakurajs/power-assert.html#/)
[power-assertの使い方 Node.js編 | Web Scratch](http://efcl.info/2014/0406/res3809/)
[power-assertでJavaScriptのテストをする ブラウザ編 | Web Scratch](http://efcl.info/2014/0411/res3820/)


### chai

[Chai](http://chaijs.com/)

should/expect/assertの好きな記法を選択でき、power-assertとは逆にAPIがたくさんある。


mock/stub
----------

アサーションライブラリと同様に自由に選択できるmochaで使う
mock/stubについての理解は以下の記事の解説がわかりやすいかと思います

[使えるRSpec入門・その3「ゼロからわかるモック（mock）を使ったテストの書き方」 - Qiita](http://qiita.com/jnchito/items/640f17e124ab263a54dd#%E3%81%A7%E3%83%A2%E3%83%83%E3%82%AF%E3%81%A3%E3%81%A6%E4%BD%95%E3%81%A7%E3%81%99%E3%81%8B)



### Sinon

[Sinon.JS - Documentation](http://sinonjs.org/)


テストランナー
---------------

### karma

[Karma - Spectacular Test Runner for Javascript](https://karma-runner.github.io/0.13/index.html) 
**ブラウザ上で**テストを走らせるためのツール。mocha等のテストフレームワークとは提供されているプラグインを使って連携する。

どのフレームワークもテストランナーを使わなくてもテストを実行できるが、Karmaを使えば複数のブラウザやデバイス上でのテストが簡単にできる。

またリモートでの実行やテストカバレッジの計測もできるらしい。


ここまでのまとめ
================

テストフレームワークを使ってテストを書いて、Karmaで走らせる。テストフレームワークにmochaを採用する場合は、アサーションやmockライブラリを別途選択する。

これにプラスしてmodule化されたJavaScriptやbabelをブラウザで使えるようにwebpackやbrowselify等を組み込むことになるかと思います。

次に実際にテスト環境を作って、簡単なテストを実行していきます。


今回の構築するテスト環境の構成
===============================

* Mocha + power-assert + Karma + babel + webpack

power-assertを使ってみたかったのでフレームワークはmochaを採用。Karmaのプリプロセッサには慣れているwebpackを使う。

### ゴール

Mochaで書いたコードをwebpackでpower-assert用のコードに変換してkarmaを使ってブラウザ上でテストを走らせる。またbabel(babel-plugin-espower)を使ってテストとテスト対象になる本体のコードをES2015を使えるようにする。

今回はwebpackとbabelを使ったES2015環境の構築の説明は省きます。

以下で作っていくテスト環境のコード
[cotto89/sample_js_test: Mocha + power-assert + Karma + babel + webpackを使ったJavaScriptテスト環境のサンプル](https://github.com/cotto89/sample_js_test)


ベースになる環境構築
=====================

webpack + babelを用いた環境を用意。今回は以下のようなディレクトリ構成で進めていく。

```
.
├── index.html
├── package.json
├── src
│   └── index.js
├── test
│   └── sample_test.js
└── webpack.config.js
```


Mochaを導入
============

```
npm i -g mocha
```

package.jsonにtaskを登録

```json:pachage.json
"scripts": { 
  "test": "mocha"
},
```  

```js:smaple_test.js
// mocha公式ドキュメントより拝借
var assert = require('assert');
describe('Array', function() {
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(1)); // ここが失敗する
    });
  });
});
```

とりあえず安心するために、テストを走らせ期待通りテストが実行されることを確認する

```
npm test
```

![default_mocha.png](https://qiita-image-store.s3.amazonaws.com/0/34837/bd79226b-dc3b-675d-fcd7-66513be5176b.png "default_mocha.png")



Karmaを導入
============

[Karma - Installation](https://karma-runner.github.io/0.13/intro/installation.html)

`karma-cli`をインストールして`karma init`。対話形式で使うブラウザやテストフレームワーク、testまでのpath等について聞かれるので適当なものを選択するとkarma.conf.jsが生成される。その際に選択した環境に必要なmoduleは自動でnpmにinstallされる。


```
npm i -g karma-cli
```

さらにpreprocessorにwebpackを使うために`karma-webpack`をインストール


```
npm i -D karma-webpack
```

preprocessorはkarmaの前処理で、webpackでブラウザで動くコードに変換されてKarmaにコードが渡る

[webpack/karma-webpack: Use webpack with karma.](https://github.com/webpack/karma-webpack)


とりあえず安心するために、Karmaを走らせて期待通りに動作することを確認する

```js:karma.conf.js
...
preprocessors: {
    // add webpack as preprocessor
    'test/*_test.js': ['webpack'],
    'test/**/*_test.js': ['webpack']
},
...
```



```json:pachage.json
"scripts": { 
  "test": "karma start"
},
```  

```
npm test
```

これでmochaで書いたテストがwebpack経由でkarmaに渡されてtestがブラウザ上で実行されることが確認できた


power-assertを導入
=================
[power-assert-js/power-assert](https://github.com/power-assert-js/power-assert)

power-assertとその他に必要なmoduleをインストール
-------------------------------------------------

power-assertとテストコードもES2015で記述できるように`babel-plugin-espower`を導入する


> ### webpack-espower-loader does not work with babel-loader!

> webpack-espower-loader does not work with babel-loader due to the change of transpiled code since babel 5.0. Please use babel-plugin-espower with babel-loader.
> 
> [power-assert-js/webpack-espower-loader: Power Assert instrumentor module for webpack](https://github.com/power-assert-js/webpack-espower-loader#webpack-espower-loader-does-not-work-with-babel-loader)


とのことなのでpower-assert + babel + webpackを使う場合は、webpack-espower-loaderは使わずにbabel-plugin-espowerを使う

また[Cannot find module Error · Issue #2 · power-assert-js/webpack-espower-loader](https://github.com/power-assert-js/webpack-espower-loader/issues/2#issuecomment-129538967)の理由でjson-loaderもインストールする

```
npm i -D power-assert babel-plugin-espower json-loader
```


karma.conf.jsを編集
-------------------

プリプロセッサで利用するwebpackをsetup

```js:karma.conf.js
...
webpack: {
  devtool: 'inline-source-map',
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015'],
          plugins: ['babel-plugin-espower']
        }
      },
      {
        test: /\.json$/,
        loader: 'json'
      }
    ]
  }
},
...
```



動作確認
--------

テストを含めてES2015で書いたコードを書いて動くことを確認。

適当なテスト対象となるコードを用意

```js:cat.js
export default class Cat {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Nyaaaan! I am ${this.name}`
  }
}
```

ES2015でテストを書いてみる

```js:sample_test.js
import assert from 'power-assert'
import Cat from '../src/cat'

describe('Cat', () => {
  var tama;
  beforeEach(() => {
    tama = new Cat('tama')
  });

  describe("greet()", () => {
    it("挨拶をする", () => {
      assert.equal(tama.greet(), 'Nyaaaan! I am neko');
    });
  });
});
```

![power-assert.png](https://qiita-image-store.s3.amazonaws.com/0/34837/c57ddcd9-bd62-56ac-183e-14a1cb8ad16f.png "power-assert.png")

 
問題なくテストが実行され、power-assertによる出力を確認することができた



まとめ
=======

細かいことはわからないけどテスト環境を作ってとりあえず動くところまではできた。一度手を動かして各ツールやライブラリの役割を把握しておくことで、他の構成で組む場合でも全体像を把握できているのでわりと楽に組めるのではと思う。

あとは各ツールやライブラリのドキュメントを読んで細かいオプションを指定するなりどんどん使いやすいようにカスタマイズしていく。

とりあえず動くとこまではきたが、webpack + babel + power-assertあたりの設定にこれでいいのか不安があるので間違っていたらアドバイスください。

[cotto89/sample_js_test: Mocha + power-assert + Karma + babel + webpackを使ったJavaScriptテスト環境のサンプル](https://github.com/cotto89/sample_js_test)