11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-19

Riot.js Advent Calendar 2016の18日目です!(遅刻組です…)

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

はじめに

今回テストするデモアプリについて以下になります。riot-routeを用いた簡単なSPAアプリですね。デザインはMaterial Design Liteを使用してます。

Riotのテストと宿命の反乱(karma-riot)と大筋はあまり変わりません。違うところは、webpackを使っているくらいです。

ではテスト環境を揃えましょう

▼必要なモジュールのインストール

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

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

ついでにpackage.jsonのscriptstestを指定しておきましょう!

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をグローバルインストールしないといけないので、書く方が面倒くさい!って方はこの一行は追加せず、コマンドラインから

$ 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の設定の変更

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.jstest.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の省略なんですね〜。

テストコード

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

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

色々コケたところ

▼何故かtagファイルが読み込まれていない

undefined is not an object (evaluating '__TAG_IMPL[tagName].class')

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

▼v3からriot.routeriot-route(別モジュール)になったこと

バージョンアップの影響で、riot-routeを外部モジュールとしてrequireする必要があります。jsファイルやtagファイルの冒頭にconst route = require('riot-route')と書いてあげましょう。

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

requireが使えない


ReferenceError: Can't find variable: require

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

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

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

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

▼es6で書くとエラーになる

function() {}の省略記法() => {} を使うと、

SyntaxError: Unexpected token '>'

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

終わりに

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

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

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

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

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

参考にしたURL

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?