karma + mocha + Riot.jsでのテスト はじめの第一歩

[Riot.js Advent Calendar 2016](http://qiita.com/advent-calendar/2016/riotjs)の18日目です！（遅刻組です…）

[Riot.js](http://riotjs.com/)（以下riot）は非常にシンプルかつ軽量で敷居も低く、とても書きやすいコンポーネント指向のUIライブラリです。（ここまでテンプレート）　今回はriotのテストに関する記事が少ないなーと思い、自分のテスト環境についてお話したいと思います。


# はじめに
今回テストするデモアプリについて以下になります。`riot-route`を用いた簡単なSPAアプリですね。デザインは[Material Design Lite](https://getmdl.io/)を使用してます。

※[Riotのテストと宿命の反乱(karma-riot)](http://qiita.com/cognitom/items/5446493eba5198139471)と大筋はあまり変わりません。違うところは、`webpack`を使っているくらいです。

- [アプリリンク](https://k-kuwahara.github.io/demo/riot/tips/routing/index.html)
- [ソース](https://github.com/k-kuwahara/k-kuwahara.github.io/tree/master/demo/riot/tips/routing)

# ではテスト環境を揃えましょう
### ▼必要なモジュールのインストール

```bash
$ npm i -D karma karma-riot karma-mocha karma-phantomjs-launcher mocha riot  
```

メインのライブラリは`mocha`と`karma`です。他にも`Jasmine`や`Chai`や`expect.js`などもありますのでお好みのものをインストールして下さい。また、`launchar`には`phantomjs`を使っています。ここは`chrome`だったり`firefox`だったり、お好みのものを…(ry　`karma-mocha-reporter`も自分の好みですのでなくても問題ないです:point_up:

ついでにpackage.jsonの`scripts`に`test`を指定しておきましょう！

```diff:package.jsonの一部
  "scripts": {
    "build": "webpack -d --watch",
    "start": "webpack-dev-server --inline --hot --progress --colors --content-base ./"
+   "test": "karma start test/karma.conf.js"
  }
}
```

これをしないと、`karma`をグローバルインストールしないといけないので、書く方が面倒くさい！って方はこの一行は追加せず、コマンドラインから

```bash
$ npm i -g karma
```

を叩いて下さい。

### ▼ディレクトリ構成
ディレクトリ構成は以下のようになっております。

```構成
./
├ node_modules
├ package.json
├ webpack.config.js
├ index.html
├ tag
│  └ app.tag
│
├ build
│  └ bundle.js   // デモアプリがwebpackを使っているためこの子が登場
│
└ test
   ├ karma.conf.js
   └ spec/app.js
```

### ▼`webpack`の設定の変更

```diff:webpack.config.js
{
   entry: {
         app: [
            './src/foo.js',
            './src/bar.js'
-         ]
+         ],
+        test: [
+           './test/spec/app.js'
+        ]
   },
   output: {
      path: __dirname + '/build/',
-     filename: 'bundle.js'
+     filename: '[name].bundle.js'
   },
   …
}
```

上記のように、test用とbuild用のバンドルファイルを個別に出力するように設定し直します。この後の`karma.conf.js`で`test.bundle.js`を読み込みます。


### ▼`karma`の設定
お次は`karma`の設定です。自分は大体以下のように書いてます。

```karma.conf.js
let TEST_PATH  = '/path_to_js_dir/test.bundle.js'
let TAG_PATH = '/path_to_tag_dir/*.tag'

module.exports = function(config) {
  config.set({
    basePath: '',   // ここに指定してもいいけど自分はあまり使わないです
    frameworks: ['mocha', 'riot'],
    plugins: [
      'karma-mocha',
      'karma-mocha-reporter',
      'karma-phantomjs-launcher',
      'karma-riot'
    ],

    // 以下がファイルを指定する部分
    files: [
      TEST_PATH,
      TAG_PATH
    ],
    preprocessors: {
      '../**/*.tag': ['riot']
    },
    port: 9876,   // 他のツールとportがバッティングしないように注意
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],   // お好みでlauncherを指定
    reporters: ['mocha'],   // デフォルトだと `progress`（指定しなくても良かったかも）
    singleRun: true,
    concurrency: Infinity
  })
}
```
細かい設定は好みのものに適宜変更して下さい。
余談ですが、`spec`って`specified`の省略なんですね〜。


# テストコード
今回のデモのテストコードはこちら！

```js:spec/app.js
var assert = require('assert')
var route = require('riot-route')
require('../../tag/app.tag')  // これが何故必要なのか不明…

describe("Routing Demo Test", function () {
   var tag = {}

   before(function () {
       // create mounting points
       var html = document.createElement('app');
       document.body.appendChild(html);
   });

   it('app mount check', function () {
      tag = riot.mount('app', {
         header: 'routing demo by riot v3',
         navs : [
            { id: 'First',  name: 'foo'},
            { id: 'Second', name: 'bar'},
            { id: 'Third',  name: 'piyo'}
         ]
      })
      assert(tag[0].isMounted === true)
      assert(document.querySelector('h2').innerText === 'No Select');
   });

   it('routing check', function () {
      // #First
      route('First')
      assert(document.querySelector('h2').innerText === 'foo');

      // #Second
      route('Second')
      assert(document.querySelector('h2').innerText === 'bar');

      // #Third
      route('Third')
      assert(document.querySelector('h2').innerText === 'piyo');
   });

   it('app unmount check', function() {
      tag[0].unmount(true)
      assert(tag[0].isMounted === false)
   });
})
```

# 実行！

上記の設定で実行すると以下のように出力されていれば成功です！

![スクリーンショット 2016-12-19 22.28.36.png](https://qiita-image-store.s3.amazonaws.com/0/48034/68fd07b7-3980-980d-f556-d98d1d47e9fc.png)


# 色々コケたところ

### ▼何故かtagファイルが読み込まれていない
```bash
undefined is not an object (evaluating '__TAG_IMPL[tagName].class')
```

先程のテストコードの3行目の部分です。ぶっちゃけ、このエラーに一番ハマりましたが原因不明です… `gulp`を使って、色々タスクを走らせながら開発しているときはこのエラーにはぶつからなかったんですが、`webpack`は奥が深いですね。。。今回はテストコードの冒頭にて必要なタグをrequireすることで回避しました。


### ▼v3から`riot.route`が`riot-route`（別モジュール）になったこと
バージョンアップの影響で、`riot-route`を外部モジュールとしてrequireする必要があります。jsファイルやtagファイルの冒頭に`const route = require('riot-route')`と書いてあげましょう。

※`karma-riot-route`があるのかな〜と思ってnpmを探しましたが見つかりませんでした。誰か作ってくれないかな〜（ワクワク）

### ▼`require`が使えない

```js

ReferenceError: Can't find variable: require
```

本来このエラーには出会うことはないはずです。というのも、上記でテストを書いたspecファイルもwebpackでバンドルしているからです。このエラーに出会うためには、__テストコードにはwebpackを使っていない__場合になります。

対処法は（実は過去に地味にハマりました）`karma-webpack`を入れて、`karma.conf.js`にて

```diff:preprocessorsの指定
   preprocessors: {
      ・・・
+     'spec/*.js': ['webpack']
   }
```

と指定してあげましょう。


### ▼es6で書くとエラーになる
`function() {}`の省略記法`() => {}` を使うと、

```js
SyntaxError: Unexpected token '>'
```

というエラーに会いました。
`webpack.config.js`の設定だと思うのですが、色々やってみて上手くいかなかったので、今回はes6は断念しました…すげぇ悔しいのでどなかたwebpackに詳しい方お知恵をお貸しいただけますと、筆者泣いて喜びます！


# 終わりに
__そもそもriotのテストはwebpackを使わなくてもいいのでは…__とか思いました（笑）この記事を書こう！と決めたときに何故気づかなかったのか…（疲れていたということにしよう←） `webpack`や`rollup`のバンドルツールは、結局生成されるファイルは読み込んだモジュールを纏めてしまったものですよね（でも今回は結果的にこのスタイルじゃないと動きませんでしたが…？）。

本来単体テストは、specファイルそれぞれのテストケースは、そのファイル内（モジュール内）で完結した方が良いと個人的には思っています。その点riotは、tagファイルが一つのモジュールなのでそこが利点でもあるんです。たまに`observable`使ってモジュール間でやり取りする場合がありますが、その場合は、それらのモジュール群を一つのモジュールと見れば良いのです。

ですので、テスト用のspecファイルまでwebpackでバンドルする必要はないなーと。もちろんバンドルする利点もありますが、ここまで来ると好みの問題です。（`karma.conf.js`の`files`で必要なtagファイルを丸っと指定して、それぞれのspecファイルでrequireした方が分かりやすいし扱いやすいんじゃないかなと）

総合的に「webpack + karma + riotでのテストはそんなに簡単ではない」と感じましたw（先人の先輩方のテストの書き方を真似しながら拡張するほうが早かった）

実際の現場では、テストはバンドルせず流して、それが問題なければ、ビルドにはwebpackやらrollupやらを使ってバンドルすればいいかなと。以上！


# 参考にしたURL
- [フロントエンドにテストを導入](http://qiita.com/howdy39/items/cdd5b252096f5a2fa438#step-4-webpack%E3%82%92%E5%B0%8E%E5%85%A5)
- [riot(公式リポジトリのtestディレクトリ)](https://github.com/riot/riot/tree/master/test)
- [Timer](https://github.com/riot/examples/tree/gh-pages/timer)