TypeScript + power-assert + Mocha + Karma + webpack を使ってブラウザ上でJavaScriptをテストする(2016/12/19追記)

github.com

2016/12/19追記

現在では様々な状況が変わっており,この記事の内容だけでは動作しません。コメント欄に追加の情報がありますのでそちらを参照してください。

やりたいこと

  • JavaScriptを書くのがつらい → TypeScriptを使って型のあるプログラミングをする
  • テストを書くためにAPIを覚えるのがダルい → power-assertを使う
  • テストをNode.js上ではなくブラウザ上で動かしたい → Karmaを使ってブラウザ上でテストを動かす

それによって生じる依存関係

  • TypeScriptを使う → TypeScriptからJavaScriptに変換しなければならない → ts-loader (TypeScriptコンパイラをwebpackから扱うためのモジュール) を使う
  • power-assertを使う → intelli-espower-loaderのようなツールを使ってJavaScriptをpower-assert用に変換しなければならない → webpack-espower-loader (power-assert用に変換するコンパイラをwebpackから使うためのモジュール) を使う
  • Karmaを使う → ts-loaderとwebpack-espower-loaderを経て変換されたJavaScriptを読み込むためにKarmaからwebpackを呼び出さなければならない → karma-webpackを使う

1. TypeScriptを使う

とりあえず,TypeScriptで書いたソースコードをwebpack, ts-loaderを使ってJavaScriptに変換し,ブラウザで実行するところまで試してみる。

まずはnpmで必要なツールを入れる。

$ npm i -D typescript webpack ts-loader

package.jsonは以下のようになった(本当はnameとversionも必須なので記入するべき)。

{
  "devDependencies": {
    "ts-loader": "^0.8.2",
    "typescript": "^1.8.10",
    "webpack": "^1.13.1"
  }
}

tsconfig.json (typescriptの設定ファイル) を書く。書き方は以下のページを参照。

Compiler Options · TypeScript

{
    "compilerOptions": {
        "noImplicitAny": true,
        "noImplicitReturns": true
    },
    "exclude": [
        "node_modules"
    ]
}

webpack.config.js (webpackの設定ファイル) を書く。

module.exports = {
  entry: {
    // エントリポイント (main関数のあるファイルみたいなもの) の場所を列挙する。
    // エントリポイントの中でrequireやimportによって指定した依存関係のあるファイルをwebpackが自動的に結合してくれる。
    // __dirnameはwebpack.config.jsのあるディレクトリ
    app: __dirname + '/src/browser/app.ts',
  },
  output: {
    // 出力先のディレクトリを指定する
    path: __dirname + '/dist',
    // 出力するファイル名
    // [name]にはentryのキーにした名前が入る。今回の例ではapp.bundle.jsになる
    filename: "[name].bundle.js",
  },
  resolve: {
    // requireやimportしたときに省略を自動的に補完してくれる拡張子の一覧
    // http://dackdive.hateblo.jp/entry/2016/04/13/123000#resolve
    extensions: ['', '.ts', '.js'],
  },
  module: {
    loaders: [
      // testに書いた正規表現にマッチするファイルをloaderに投げる
      // loaderを複数指定したときには右から左に適用される
      // http://dackdive.hateblo.jp/entry/2016/04/13/123000#moduleloaders
      { test: /\.ts$/, loader: 'ts-loader' },
    ],
  },
};

ブラウザ上で実行するためのHTMLとTypeScriptを書く。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>index.html</title>
</head>
<body>
    <script src="dist/app.bundle.js"></script>
</body>
</html>
// src/browser/app.ts
import {add} from "./sub";
console.log(add(2, 3));
// src/browser/sub.ts
export function add(x: number, y: number): number {
    // あとでテストの結果を見るときのためにわざと間違えている
    return x * y;
}

ビルドしてTypeScriptからJavaScriptに変換する。

$ $(npm bin)/webpack

ブラウザで確認するとconsole.logの結果がコンソールに出ている(出力は5じゃなくて6だけど)。

今のディレクトリ構成はこんな感じ。

.
├── dist
│   └── app.bundle.js
├── index.html
├── node_modules/
│   └── 略
├── package.json
├── src
│   └── browser
│       ├── app.ts
│       └── sub.ts
├── tsconfig.json
└── webpack.config.js

Mochaとpower-assertでテストを書く

ブラウザ上テストを動かす前に,まずはNode.js上でテストを動かしてみる。

まずは必要なツールを入れる。intelli-espower-loaderはJavaScriptをpower-assert用に変換するモジュール。typingsはTypeScript用の型定義ファイルの管理ツール (Mochaとpower-assertの型定義ファイルを入れたい)。

$ npm i -D mocha power-assert intelli-espower-loader typings

typingsを使って型定義ファイルをダウンロードする。

$ $(npm bin)/typings install --save --global dt~mocha dt~power-assert

power-assertが依存しているファイルが足りないというメッセージが表示されたので,足りていないファイルもダウンロードする(自動的に依存関係を解決してくれないのはなぜ?)。

$ $(npm bin)/typings install --save --global dt~empower dt~power-assert-formatter

TypeScriptでテストを書く。今回は tsc コマンドでコンパイルするのでreference pathの記述が必要。

// test/browser/sub_test.ts
/// <reference path="../../typings/index.d.ts" />
import * as assert from "power-assert";
import {add} from "../../src/browser/sub";

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

テストコードをTypeScriptからJavaScriptに変換する。今回はお試しなので tscコンパイルする。

$ $(npm bin)/tsc test/browser/sub_test.ts

テストを実行する

$ $(npm bin)/mocha --require intelli-espower-loader test/browser/sub_test.js

結果がかっこいい。

  1)  2 + 3 = 5:

      AssertionError:   # test/browser/sub_test.js:6
  
  assert(sub_1.add(2, 3) == 5)
         |     |         |    
         |     6         false
         Object{add:#function#}
  
  [number] 5
  => 5
  [number] sub_1.add(2, 3)
  => 6

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

必要なツールを入れる。

$ npm i -D karma karma-webpack webpack-espower-loader

Karmaの設定ファイルを生成する。

$ (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".

package.jsondevDependencies にKarmaが必要としているパッケージが追加されているので,インストールする。

$ npm install

karma.conf.js に以下の内容を追加して,webpackを呼び出してテストコードを変換することについて記述する。

+preprocessors: {
+    'test/browser/*.ts': ['webpack'],
+},
+
+webpack: require(__dirname + '/webpack.config.js'),

webpack.config.js の中身も編集する。_test.tsで終わるテスト用のコードは,ts-loaderでTypeScriptからJavaScriptに変換した後にwebpack-espower-loaderでpower-assert用に変換する。

   module: {
     loaders: [
       { test: /\.ts$/, loader: 'ts-loader' },
     ],
+    postLoaders: [
+      { test: /_test\.ts$/, loader: 'webpack-espower-loader' }
+    ],
   },
 };

Karmaでテストを実行してみる。

$ $(npm bin)/karma start

Module parse failed: node_modules/estraverse/package.json Unexpected token なる 謎のエラー が出たのでjson-loaderを入れる。

$ npm i -D json-loader

webpack.config.js も編集する。

   module: {
     loaders: [
       { test: /\.ts$/, loader: 'ts-loader' },
+      { test: /\.json$/, loader: 'json-loader' },
     ],
     postLoaders: [
       { test: /_test\.ts$/, loader: 'webpack-espower-loader' },
     ],
   },
 };

気を取り直して実行する。

$ $(npm bin)/karma start

実行できた。

AssertionError:   # sub_test.ts:6

assert(sub_1.add(2, 3) == 5)
       |     |         |    
       |     6         false
       Object{add:#function#}

[number] 5
=> 5
[number] sub_1.add(2, 3)
=> 6

まとめ

大変だった… 闇しか感じない…

追記

多段SourceMapを実現するにはどうすれば良いのだろう?