2016/01/02
始めに
最近vue-resource・vue-routerを使ったSPAウェブサービスを開発・運用しており、ふとそろそろUnit Testを書いてみたいな〜と思い、ググってみたのですが思っていた以上に情報が見つからず・・・。ネット上で見つけたのは下記の3つのみ。
公式サイトを見ながら見よう見まねで書いてみたが、ハマりにハマってしまいもう無理だーとVue.jsコミュニティの方々に助けを求めました。皆さんから多数の提案を頂き、無事に解決しました。本当にありがとうございます!
今回はVue.jsコミュニティの方々に教えて頂いた、Vue.js componentのUnitTestを書く方法をまとめます。
前提
下記ライブラリを使用します。
- Vue.js 2.0 + vue-resource + vue-router + vueify
- karma
- mocha
- chai
テストケース 3パターン
- 普通のコンポーネントメソッド
- vue-routerのパラメーターを利用したメソッドのテスト
- vue-resourceでAPI呼び出しをしているメソッドのテスト
テストについて
- Vue.jsの
*.vueのコンポーネントを対象とし、methods内のメソッドをテストします。 - 一部省略していますが、こちらにサンプルコードが置いてありますのでこちらをご覧ください。
パターン1:普通のコンポーネントメソッド
まずは外部ライブラリ、リソースを全く利用しない一番シンプルな例です。下記のようなaddという足し算メソッドがあったとします。
|
1 2 3 4 5 6 7 |
export default { methods: { add(a, b) { return a + b; } } } |
上記のメソッドのテストをする場合は以下のように記述します。ポイントは以下の通りです。
* テスト対象のコンポーネントをimportします。(読み込み時にファイルパスは適度の調整してください。)
* コンポーネント内のメソッドへはmethodsを通じて呼び出すことができます。これはVueの基本仕様通りメソッドはmethods内にあるからです。
|
1 2 3 4 5 6 7 |
import Test from '../../../src/js/components/test.vue'; describe('Testコンポーネント', () => { it('足し算', () => { expect(Test.methods.add(1, 2)).to.be.eql(3); }) }); |
また下記のようにすればメソッドの存在確認テストもできます。
|
1 2 3 4 5 6 7 |
import Test from '../../../src/js/components/test.vue'; describe('Testコンポーネント', () => { it('足し算', () => { expect(Test.methods.add).to.be.a('function'); }) }); |
ここまでは自分で出来ました。
パターン2:vue-routerのパラメーターを利用したメソッドのテスト
ここからは自分では解決できず、コミュニティの方の助言で解決しました。例えば下記のように vue-router のパラメーターを取得して文字列結合をするとします。
|
1 2 3 4 5 6 7 |
export default { methods: { myName() { return this.$route.params.name + ' Taro'; } } } |
上記のメソッドのテストをする場合は以下のように記述します。ポイントは以下の通りです。
- Vue.extendでサブクラスを作成し、テストケースの中でインスタンス化します
- またインスタンス化時にbeforeCreateでルーティングのパラメーターを設定することが出来、ルーティングされた体でテストを書くことが出来るようになります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import Vue from 'vue'; import _Test from '../../../src/js/components/test.vue'; const Test = Vue.extend(_Test); describe('Testコンポーネント', () => { it('vue-routerを利用した場合のテストケース', function() { const vm = new Test({ beforeCreate() { this.$route = { params: { name: 'Yamada' } } } }); expect(vm.myName()).to.be.eql('Yamada Taro') }); }); |
パターン3:vue-resourceでAPI呼び出しをしているメソッドのテスト
今回一番悩んだのがこのテストケースでした。APIを利用してJSONで値を取得し、success時にデータオブジェクトのprofileを変更したい場合です。API呼び出しには vue-resourceのグローバル関数 Vue.http.get を利用していました。メソッドの返り値はPromiseとなり、非同期処理のテストってどうやるのか・・・。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
export default { data() { return { profile: {} } }, methods: { fetchProfile() { Vue.http.get('http://localhost:8000/api/profile').then((response) => { console.log("success"); this.profile = response.json(); }, (response) => { console.log("failure"); }); } } } |
ゴールのイメージとしては外部リソースであるAPIを実行しないようにモック化し、テスト対象のメソッド内で置き換えをすることです。
今回は Vue.http.getとグローバル関数を呼ぶので、下記のように算出プロパティを利用して$Vueからテスト対象オブジェク自身を取得できるように調整します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
export default { data() { return { profile: {} } }, computed: { $Vue() { return Vue; } }, methods: { fetchProfile() { Vue.http.get('http://localhost:8000/api/profile').then((response) => { console.log("success"); this.profile = response.json(); }, (response) => { console.log("failure"); }); } } } |
次にテストケースです。ポイントは以下の通りです。
- まずテストケースの引数にdoneを指定します。mochaの仕様でdone()の実行をもって非同期テストが終了したことをmochaに通知します。(通例的に
doneと命名されるそうです) Vue.http.getでAPIを実際に呼び出さないようにモック化。getメソッドでPromiseの成功時にjson()メソッドがprofileのオブジェクトを返すように設定します。sinon等のモックライブラリを使わず、今回は仮の値を返す方式でも問題ありません。そしてテストケース2と同じ方式でコンポーネントをインスタンス化し、今回のテストケース用に設定した$Vueを通じて、Vueインスタンスのグローバル変数を上書きします。- 最後にテスト対象のメソッドを実行します。今回は非同期のテストにつき、そのままではPromiseの成功処理に入らないので、setTimeoutで擬似的に非同期にします。setTimeout内のdone()の実行をもって非同期テストが終了したことをmochaに通知され、モックで指定した通り
Promise.resolveで成功処理に入りテストケースが通ります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import Vue from 'vue'; import _Test from '../../../src/js/components/test.vue'; const Test = Vue.extend(_Test); describe('Testコンポーネント', () => { // ステップ1:引数のdoneを指定 it('vue-resourceを利用した場合', (done) => { let profile = { nickname : 'hoge' }; // ステップ2:Vue.http.getをモック化 const vm = new Test() vm.$Vue.http = { get () { return Promise.resolve({ json () { return profile } }); } } // ステップ3:setTimeoutで非同期呼び出し vm.fetchProfile(); setTimeout(() => { expect(vm.profile).to.be.eql(profile) // mochaの非同期用テスト仕様で、done()が実行されるまでテストは実行されない。 done(); }, 0) }); }); |
Special Thanks
今回はVue.jsコミュニティの下記の方々に1週間に渡って何度も助言頂きました。最後にはパッチまで準備して頂きありがとうございます!とても勉強になりました。
感想
普段からJavaScriptのテストコードを全く書いたことがなかったので、正直基本的な事から理解が出来ず苦労しました。ただVue.jsを通じてテストを書く機会が生まれて、この3パターンなら書けるようになりました。JavaScriptのテストコードコード楽しいですね〜。もっと書きたくなったぞー!