Karma + Mocha でRequreJSを使ったJavaScriptをテストする

たまにやると忘れてるのでメモ。
Karmaはnode.jsベースのテストランナーで、ターミナルからテストを実行できて、複数のブラウザを立ち上げてテストを実行したり、ファイルの変更を監視して自動でテストを実行したりもできます。
RequireJSを使ってるJavascriptに対して、テストフレームワークとしてmocha、アサーションライブラリとしてexpectを使ったテストを書き、Karmaで実行します。

※2014/3/12追記 使用したバージョンは以下。

  • node v0.10.25
  • npm 1.3.24
  • karma 0.12.0

追記ここまで。

最終的にはこんな感じに。

$ tree
.
|-- karma.conf.js
|-- package.json
|-- src
|   `-- js
|       |-- app.js
|       |-- lib
|       |   |-- require.js
|       |   `-- underscore.js
|       `-- require.config.js
`-- test
    `-- js
        |-- appSpec.js
        `-- require.config.js

コードの準備

プロダクトコードがsrc/app.js、RequireJSとUnderscore.jsはダウンロードしてsrc/libへ。無理矢理Underscore.jsを使ったプロダクトコードを用意してみる 。

// src/app.js
define(['underscore'], function(){
  return {
    max: function(a, b, c){
      return _.max([a, b, c]);
    }
  };
});

テストコードがtest/appSpec.js。

// test/appSpec.js
define(['app'], function(app){

  describe('appモジュールのテスト', function(){
    it('maxメソッドのテスト', function(){
      expect(app.max(1,2,3)).to.be(3);
    });
  });

});

package.jsonを用意。

{
  "name": "karma-sample",
  "version": "0.0.1"
}

Karmaをインストール

Karmaをインストールする。

$ npm install -g karma-cli
$ npm install --save-dev karma

Karmaの設定ファイルを作成

karma initで設定ファイルを生成

Karma initを実行して質問に答えていく。そうするとkarma.conf.jsが生成される。

$ 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.
> yes

Do you want to capture a browser 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.
> src/**/*.js
> test/**/*Spec.js
>_

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.
> src/require.config.js
WARN [init]: There is no file matching this pattern.

>_

Which files do you want to include with <script> tag ?
This should be a script that bootstraps your test by configuring Require.js and kicking __karma__.start(), probably your test-main.js file.
Enter empty string to move to the next question.
> test/require.config.js
WARN [init]: There is no file matching this pattern.

>

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

Karmaのプラグインをインストール

Karmaで、requirejs、mocha、expectを使うためのプラグインをインストールする。

$ npm install --save-dev karma-expect
生成されたkarma.conf.jsを確認

生成されたkarma.conf.jsを中身を見るといろいろと設定項目が。
frameworksは、karmaが使うプラグインを指定する。mochaとrequirejsが記述されているが、expectが記述されていないので追記する。

// frameworks to use
frameworks: ['mocha', 'requirejs', 'expect'],

filesには、テストで使うjsファイルを指定する。karma init時に指定したパターンとtest/require.config.jsが記述されている。

// list of files / patterns to load in the browser
files: [
  'test/require.config.js',
  {pattern: 'src/**/*.js', included: false},
  {pattern: 'test/**/*Spec.js', included: false}
],

'test/require.config.js'のように単純にファイル(ワイルドカード可)を指定する方法と、{pattern: 'src/**/*.js', included: false}のようにJSONでオプションと併せて指定することもできる。
src/**/*.jstest/**/*Spec.jsには、included: falseというオプションがついているが、これはkarmaがブラウザを立ち上げたときにscriptタグでjsファイルを読み込まないための指定。scriptタグではなくRequireJSを使ってjsファイル読み込むのでこのオプションが付与される。

excludeには、テスト実行時に読み込みたくないファイルを指定。karma init時に指定したパターンが記述される。テスト用のrequire.config.jsを使うため、プロダクトコード用のrequire.config.jsを指定してテスト実行時には読み込まないようにする。

// list of files to exclude
exclude: [
  'src/require.config.js'
],

他の設定項目は、RequireJSを使わない場合といっしょ。

テスト用のrequire.configを設定

テスト実行時はプロダクトコード用のrequire.configを使わない。代わりにテスト用のrequire.configを設定する。
プロダクトコード用のrequire.configとの違いは、baseUrlとdepsとcallback。

// test/require.config.js
var tests = [];
for (var file in window.__karma__.files) {
  if (window.__karma__.files.hasOwnProperty(file)) {
    if (/Spec\.js$/.test(file)) {
      tests.push(file);
    }
  }
}

require.config({
  baseUrl: "/base/src",
  paths: {
    "underscore": "lib/underscore"
  },
  shim: {
    "underscore": {
      exports: "_"
    }
  },
  deps: tests,
  callback: window.__karma__.start
});
  • baseUrl
    • /baseで始まるパスを設定する。karmaを実行すると、http://localhost:9876/base がkarma.conf.jsが存在するディレクトリになる。そのため、プロダクトコードのbaseUrlと同じディレクトリを指すように、baseUrlを/base/srcに設定する。
  • deps
    • テストコードを指定する。window.__karma__.filesにkarma.conf.jsのfilesで指定したファイルが格納されているので、テストコード(ファイル名がSpec.jsで終わっているファイル)のみをtests配列に格納して、depsに指定する。
  • callback
    • window.__karma__.startを指定する。これで、depsに記述したテストコードが全てブラウザに読み込まれた後に、window.__karma__.startが実行されテストが動作する。

karmaを実行

これで準備ができたので、あとは以下のコマンドで実行。

$ karma start karma.conf.js

Gruntでkarma実行

gruntからkarmaを実行できると他のタスクと組み合わせたりできて便利。ということで、gruntとgrunt-karmaをインストール。

$ npm install --save-dev grunt grunt-karma

karmaタスクを定義して、

// Gruntfile.js
module.exports = function(grunt){
  grunt.initConfig({
    karma: {
      unit: {
        configFile: 'karma.conf.js'
      }
    }
  });

  grunt.loadNpmTasks('grunt-karma');
};

実行する。

$ grunt karma