karma、mocha、power-assert、webpackで Node.js と Webブラウザ 兼用の TypeScript コードをテストする


# 利用ツール群

* [karma](https://karma-runner.github.io/1.0/index.html)・・・テストランナー。もともとはAngularJSのチームが開発した。
	* テスト実行の流れを管理する。
	* テスティングフレームワークを呼び出したり、ブラウザ向けテスト時にブラウザを起動したりする。
* [mocha](https://mochajs.org/)・・・テスティングフレームワーク。
	* BDD や TDD などを可能するフレームワーク。
* [power-assert](https://github.com/power-assert-js/power-assert)・・・アサーションライブラリ。
	* テストで利用する便利なアサーション群をまとめたライブラリ。
* [webpack](https://webpack.js.org/)・・・複数のファイルを単一モジュールにまとめる。

# 参考URL

 以下の記事を参考にしました。

* [TypeScript + power-assert + Mocha + Karma + webpack を使ってブラウザ上でJavaScriptをテストする](http://kujira16.hateblo.jp/entry/2016/07/29/215735)

# テスト対象コード

* app.tsが後々、webpackでバンドルにまとめる際のエントリポイントになります。

```src/browser/app.ts
import {sub} from "./sub";
import {add} from "./add";
console.log("add = " + add(2, 3));
console.log("sub = " + sub(2, 3));
```

```src/browser/add.ts
export function add(x: number, y: number): number {
    return x + y;
}
```

```src/browser/sub.ts
export function sub(x: number, y: number): number {
    return x - y;
}
```

# テストコード

```test/browser/add.spec.ts
import * as assert from "power-assert";
import {add} from "../../src/browser/add";

it("2 + 3 = 5", () => {
    assert(add(2, 3) == 5);
});
```

```test/browser/sub.spec.ts
import * as assert from "power-assert";
import {sub} from "../../src/browser/sub";

it("2 - 3 = -1", () => {
    assert(sub(2, 3) == -1);
});
```

# npmモジュールをインストールする

* @types/mocha、@types/power-assertはそれぞれmochaとpower-assertの型定義ライブラリ。TypeScriptで参照する際に必要。

```
npm install @types/mocha @types/power-assert mocha power-assert intelli-espower-loader typescript --save-dev
```

# TypeScriptコードをビルドする

* 出力先をdistとする。
* TypeScript2以降は、[typesとしてmochaを指定する](http://stackoverflow.com/questions/39816482/how-to-import-describe-and-it-from-mocha-in-typescript)必要あり。

```tscconfig.json
{
    "compilerOptions": {
        "noImplicitAny": true,
        "noImplicitReturns": true,
        "types": [
            "mocha"
        ],
        "outDir": "./dist"
    },
    "exclude": [
        "node_modules"
    ]
}
```

* 以下のコマンド、TypeScriptのコードがビルドされ、JavaScriptコードが出力される。

```
$(npm bin)/tsc -p .
```

# mocha と power-assert を使い、 Node.js 上で実行する

* mocha にはテストコードのJavaScriptを指定する必要あり。
	* find コマンドでマッチするテストコードを渡すことも可能。

```
$ $(npm bin)/mocha --require intelli-espower-loader $(find ./dist/test -name '*.js')


  ✓ 2 + 3 = 5
  ✓ 2 - 3 = -1

  2 passing (9ms)
```

# Karmaを使ってブラウザ上でテストを実行する

* karmaの設定ファイルを生成する。
  * 今回は、WebブラウザのテストはChromeのみ。
  * それ以外のブラウザでもテストしたい場合は、他のlancherをインストールする。ただし、ブラウザがローカルにインストールされていないと実行できない。
    * [karma-firefox-launcher](https://www.npmjs.com/package/karma-firefox-launcher)
    * [karma-safari-launcher](https://www.npmjs.com/package/karma-safari-launcher)
    * [karma-ie-launcher](https://www.npmjs.com/package/karma-ie-launcher)
    * [karma-phantomjs-launcher](https://www.npmjs.com/package/karma-phantomjs-launcher)

```
$ (npm bin)/karma init

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
> 

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> test/browser/*.ts
> 

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
> 

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes

Config file generated at "/??/karma.conf.js".
```

* karma.conf.jsが生成される。
* webpackでテストコードを変換するため、preprocessorsの箇所にwebpackの設定を追記している。
* TypeScriptをサポートするにはmimeの設定も必要。

```karma.conf.js
// Karma configuration
// Generated on Fri Jul 29 2016 21:29:35 GMT+0900 (JST)

module.exports = function (config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha'],


    // list of files / patterns to load in the browser
    files: [
      'test/browser/*.ts'
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      'test/browser/*.ts': ['webpack'],
    },


    webpack: require(__dirname + '/webpack.config.js'),


    // https://github.com/angular/angular-cli/issues/2125#issuecomment-247395088
    mime: {
      'text/x-typescript': ['ts', 'tsx']
    },


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: true,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  })
}

```

* karama init 実行後にパッケージが追加されているので、再度、npm installを実行すること。

```
$ npm install
```

* 最終的な package.json は以下の通り。

```package.json
{
  "devDependencies": {
    "@types/mocha": "^2.2.33",
    "@types/power-assert": "^1.4.29",
    "intelli-espower-loader": "^1.0.1",
    "json-loader": "^0.5.4",
    "karma": "^1.3.0",
    "karma-chrome-launcher": "^2.0.0",
    "karma-mocha": "^1.3.0",
    "karma-webpack": "^1.8.0",
    "mocha": "^3.2.0",
    "power-assert": "^1.4.2",
    "ts-loader": "^1.3.3",
    "typescript": "^2.1.4",
    "webpack": "^1.14.0",
    "webpack-espower-loader": "^1.0.1"
  },
  "scripts": {
    "test": "karma start --single-run"
  }
}
```

* karmaから実行される、webpackの設定ファイルは以下の通り。
* app.tsをbundleにまとめる。
* module loardersを追記している。
	* テスト用のコードは，ts-loaderでTypeScriptからJavaScriptに変換した後にwebpack-espower-loaderでpower-assert用に変換する。

```webpack.conf.js
module.exports = {
  entry: {
    app: __dirname + '/src/browser/app.ts',
  },
  output: {
    path: __dirname + '/dist',
    filename: "[name].bundle.js",
  },
  resolve: {
    extensions: ['', '.ts', '.js'],
  },
  module: {
    loaders: [
      { test: /\.ts$/, loader: 'ts-loader' },
      { test: /\.json$/, loader: 'json-loader' },
    ],
    postLoaders: [
      { test: /_test\.ts$/, loader: 'webpack-espower-loader' },
    ],
  },
};
```

* karmaを起動するとWebブラウザが立ち上がりテストが実行される。

```
$(npm bin)/karma start
（省略）
20 12 2016 23:36:06.276:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/
20 12 2016 23:36:06.279:INFO [launcher]: Launching browser Chrome with unlimited concurrency
20 12 2016 23:36:06.287:INFO [launcher]: Starting browser Chrome
20 12 2016 23:36:07.513:INFO [Chrome 55.0.2883 (Mac OS X 10.12.2)]: Connected on socket /#5Kf5li8bV2zNxwJJAAA
A with id 64264329
Chrome 55.0.2883 (Mac OS X 10.12.2): Executed 2 of 2 SUCCESS (0.004 secs / 0.003 secs)
```