Riotのテストと宿命の反乱(karma-riot)


AngularのテストではおなじみのKarma(=宿命、業)ですが、すごい名前ですよね...。`karma`のRiotプラグインは日本語だと「宿命の反乱」でしょうか。フロントエンドのテストの話です、念のため。

というわけで、Riotの公式リポジトリにこの[Karmaプラグイン](https://github.com/riot/karma-riot)が仲間入りしたので、簡単に要所をまとめたいと思います。

## アプリケーション構成(例)

極小構成ということで、この記事の登場人物はこの5ファイルだけです。動いているのをまず触りたいという方は、[こちらにPlunkerで編集可能なもの](http://riotjs.com/examples/plunker/?app=router-page-switcher)があります。[ソースとテスト一式](https://github.com/riot/examples/tree/master/router-page-switcher)はこちら。

- app.tag
- index.html
- karma.conf.js
- package.json
- test/
  - specs.js

![Riot Router Example: Page Switching.png](https://qiita-image-store.s3.amazonaws.com/0/1858/3bf66b8b-0950-88e5-c430-7261bf323310.png "Riot Router Example: Page Switching.png")

## アプリケーション本体

まず`app.tag`ですが、簡単なルーティングのデモになっています。ここではテストの説明が主眼なので、細かい話は略。

```html
<app>
  <nav><a each={ data } href="#{ id }">{ title }</a></nav>

  <article>
    <h1>{ page.title || 'Not found' }</h1>
    <p>{ page.body || 'Specified id is not found.' }</p>
  </article>

  <script>
    var self = this
    self.data = [
      { id: "", title: "Home", body: "Click the link above." },
      { id: "1", title: "First", body: "This is the first page." },
      { id: "2", title: "Second", body: "This is the second page." }
    ]
    self.page = self.data[0]

    riot.route(function(id) {
      self.page = self.data.filter(function(r) { return r.id == id })[0] || {}
      self.update()
    })
  </script>
</app>
```

アプリケーションとしては、これ(↑)を`index.html`から呼び出すだけ。

```html
<!doctype html>
<html>
  <head>
    <title>Riot Router Example: Page Switching</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
  </head>
  <body>
    <app></app>
    <!-- riot tags -->
    <script type="riot/tag" src="app.tag"></script>
    <!-- scripts we need -->
    <script src="https://cdn.jsdelivr.net/g/riot@2.2(riot.min.js+compiler.min.js)"></script>
    <!-- mount this app -->
    <script> riot.mount('app') </script>
  </body>
</html>

```

## 必要なライブラリのインストール

少なくとも、次の3つは必ずインストール。

```bash
$ npm install --save-dav riot karma karma-riot
```

この辺も入れておきましょう。※好みのライブラリに適宜差し替えてください。

```bash
$ npm install --save-dav expect.js karma-mocha karma-mocha-reporter karma-phantomjs-launcher
```

`package.json`の中身はこんな感じになっているはずです。あと、テストの起動用に`scripts`のところに記述をお忘れなく。

```json
{
  "devDependencies": {
    "expect.js": "^0.3.1",
    "karma": "^0.13.9",
    "karma-mocha": "^0.2.0",
    "karma-mocha-reporter": "^1.1.1",
    "karma-phantomjs-launcher": "^0.2.1",
    "karma-riot": "^1.0.0",
    "riot": "^2.2.4"
  },
  "scripts": {
    "test": "karma start"
  }
}
```

## karmaの設定

さて、このアプリケーション(と呼んでいいのか...)をテストしてみたいと思います。karmaの出番です。`jasmine`でもOKですが、ここではなんとなく`mocha`で。`karma.conf.js`はこんな感じに。

```javascript
module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['mocha', 'riot'], // フレームワークにriotを指定
    plugins: [
      'karma-mocha',
      'karma-mocha-reporter',
      'karma-phantomjs-launcher',
      'karma-riot' // プラグイン指定
    ],
    files: [
      'node_modules/expect.js/index.js', // chaiとか好きなものでOK
      '**/*.tag', // タグファイルの指定
      'test/**/*.js' // テストファイルの指定
    ],
    preprocessors: {
      '**/*.tag': ['riot'] // プリプロセッサにriotを指定
    },
    browsers: ['PhantomJS'],
    reporters: ['mocha'],
    singleRun: true
  })
}
```

karmaは`files`で指定したファイルと、フレームワークで必要とするファイルをテスト時に自動的に読み込みます。その際、CoffeeScriptを自動変換したりするのに使えるのが、プリプロセッサです。`karma-riot`プラグインが読み込まれていれば、Riotのタグもよしなにkarmaが変換してくれます。

プロダクション用をBrowserifyで1ファイルに固めてると、単一タグのテスト用に別途コンパイルが必要だったりしますが、このプリプロセッサがあればそれも不要ですね。

その他の部分は、ごく一般的なkarmaの設定です。詳しくは[karma](http://karma-runner.github.io/0.13/index.html)の公式サイトをどぞ。

## テスト

ここまででお膳立てはできたので、テスト本体です。ここでは`document.body`の直下にDOMを追加して、そこにテストしたいタグをマウントする形をとりました。例として、(1)タグのマウントの確認、(2)ルーターの挙動をチェックするテストを入れています。

```javascript
describe('Application specs', function() {

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

  it('タグのマウント', function() {
    riot.mount('app')
    expect(document.querySelector('article > h1').textContent)
      .to.be('Home')
  })

  it('別URLに移動して表示内容を切り替え', function() {
    riot.route('1') // http://localhost/1
    expect(document.querySelector('article > h1').textContent)
      .to.be('First')

    riot.route('2') // http://localhost/2
    expect(document.querySelector('article > h1').textContent)
      .to.be('Second')
  })

})
```

例では1ファイルにまとめていますが、長くなってきたら、テストのカテゴリーごとにファイルに分けていきます。

## テストの実行

さあ、あとは...

```bash
$ npm test
```

するだけです。全部のテストがクリアなら"xx tests completed"と表示されます。

![router-page-switcher — bash — 93×20.png](https://qiita-image-store.s3.amazonaws.com/0/1858/0615238c-fb5e-f905-d398-ac4983322166.png "router-page-switcher — bash — 93×20.png")


## まとめ

Riotは見た目のHTMLっぽさが際立っていて、テストといってもピンとこないかもですが...、2ファイル(`karma.conf.js`とテストファイル)を足すだけから始められます。やんなきゃ!

あとは、APIを叩くテストが入るなら[Sinon.JS](http://sinonjs.org/)を叩くもよし、単体テストで切り出せるならmochaだけでテストしてもよし、普段のやり方でどぞ。