今回はVue.jsでユニットテストを行う方法をまとめました
Vue.jsのユニットテストとは、コンポーネント単位のテストです
コンポーネントに対するインプットで期待するアプトプットを得られるかをテストします
インプットとはdata,propsなどコンポーネントの状態、クリックなどのユーザーイベントなどで
アウトプットとはHTMLの描画や更新後のコンポーネントの状態になります
目次
テストツール
Vue.jsでテストを行うには、VueのコンポーネントテストライブラリとJavascriptのテストライブラリを組み合わせて行います。
今回は公式が推奨している下記のライブラリを利用します。
https://v3.ja.vuejs.org/guide/testing.html
Vue Test Utils
Vue公式単体テストライブラリであるVue Test Utilsを使ってテストコードを書きます
Jest
Meta(Facebook)社によって開発されたJavaScriptテストフレームワーク
Vue Test Utilsで書いたテストコードをJestで実行します
環境構築
自動設定
vue cliで作成したプロジェクトであれば以下のコマンドで必要なパッケージ、設定を自動で行ってくれます
1 |
vue add unit-jest |
手動で設定
手動で追加する場合はVueバージョンによって必要なパッケージのバージョンも異なるので注意してください
今回はVue3を対象としています。
- VueTestUtilsをインストール
1 |
yarn add --dev @vue/test-utils@next |
- Jestをインストール
1 |
yarn add --dev jest |
- Jestがvueファイルを扱えるようにvue-jestをインストール
1 |
yarn add --dev @vue/vue3-jest |
- JestがES moduleを扱えるようにbabel-jestをインストール
1 |
yarn add --dev babel-jest |
- 設定の追加
必要なパッケージはインストールできたので、package.jsonに設定を追加します
- testEnvironment
- 実行環境をnodeからブラウザ環境へ変更するため
- moduleFileExtensions
- テスト対象のフィアルの拡張子
- moduleNameMapper
- importで@を/srcのエイリアスとして設定
- transform
- JestがBabelとVueファイルを扱うための設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
"jest": { "testEnvironment": "jsdom", "moduleFileExtensions": [ "js", "json", "vue" ], "moduleNameMapper": { "^@/(.*)$": "<rootDir>/src/$1" }, "transform": { "^.+\\.js$": "babel-jest", "^.+\\.vue$": "@vue/vue3-jest" } |
テストコードの実装例
下記がテスト対象のコンポーネントです
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<template> <div> <span class="count">{{ count }}</span> <button @click="increment">Increment</button> </div> </template> <script> export default { name: 'CounterComponent', data() { return { count: 0 } }, methods: { increment() { this.count++ } } } </script> |
tests/unitディレクトリ内の.spec.jsファイルがテストの対象となります
mountメソッドへテスト対象となるコンポーネントを渡すことで、コンポーネントをラップしたWrapperオブジェクトが作成され、このWrapperオブジェクトを使ってコンポーネントのテストコードを書いて、Jestでテストを実行します。
下記ではコンポーネントが描画したHTMLに<span class=”count”>0</span>が含まれているか検証しています
1 2 3 4 5 6 7 8 9 10 |
import { mount } from '@vue/test-utils' import CounterComponent from '@/components/CounterComponent'; describe('Counter', () => { const wrapper = mount(Counter) it('renders the correct markup', () => { expect(wrapper.html()).toContain('<span class="count">0</span>') }) }) |
イベントトリガー
ユーザーの入力イベントはtriggerを使いイベントを発火させます
下記ではボタンクリックを実行しています
1 2 3 4 5 6 |
it('button click should increment the count', () => { expect(wrapper.vm.count).toBe(0) const button = wrapper.find('button') button.trigger('click') expect(wrapper.vm.count).toBe(1) }) |
data,propsの更新
setData
メソッドまたは setProps
メソッドを使って、コンポーネントの状態を操作することができます
1 2 3 4 5 6 |
it('set state', async () => { await wrapper.setProps({ title: '10antz' }) await wrapper.setData({ count: 10 }) expect(wrapper.html()).toContain('<span class="count">10</span>') expect(wrapper.find('span.title').text()).toBe('10antz') }) |
また、下記のようにwrapper.vmからインスタンスのプロパティーやメソッドにアクセスすることができます
1 2 |
console.log('title', wrapper.vm.title); console.log('count', wrapper.vm.count); |
ルーティングのテスト
ルーティングによる遷移のテストなど、複数のコンポーネントが関係するテストはユニットテストでは扱えず、
VueRouterの機能に依存するコンポーネントについてのテストを扱います($routeや$routerなど)
例えばコンポーネントで$routeのparamsから情報を取得する場合
global.mocksマウントオプションを使用することで、コンポーネントへ$routeを渡すことができます
1 2 3 4 5 6 7 8 9 10 11 |
export default { name: 'RouterComponent', data() { return { id: 0 } }, mounted() { this.id = this.$route.params.id; } } |
1 2 3 4 5 6 7 8 9 10 |
import { mount } from '@vue/test-utils' import RouterComponent from '@/components/RouterComponent'; test('router test', () => { const $route = { params: { id: '123' } } const wrapper = mount(Router, { global: { mocks: { $route }} }) expect(wrapper.vm.$data.id).toBe('123') }) |
Vuexのテスト
Vuexを使用してコンポーネントをテストするにはglobal.pluginsマウントオプションを使用してvuexのストアをインストールすることで可能になります
下記の例ではボタンクリックでストアのcountをIncrementさせるコンポーネントをテストしています
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<template> <div> <span class="count">{{ count }}</span> <button @click="increment">Increment</button> </div> </template> <script> export default { name: 'VuexComponent', computed: { count() { return this.$store.state.count } }, methods: { increment() { this.$store.commit('increment'); } } } </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { createStore } from 'vuex' export default createStore({ state() { return { count: 0 } }, mutations: { increment(state) { state.count += 1 } } }) |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { mount } from '@vue/test-utils' import VuexComponent from '@/components/VuexComponent'; import store from '@/store/index' test('vuex test', async () => { const wrapper = mount(VuexComponent, { global: { plugins: [store] } }) await wrapper.find('button').trigger('click') expect(wrapper.html()).toContain('<span class="count">1</span>') }) |
テストコードからストアを直接更新することも可能です
1 |
store.commit('increment') |
HTTPリクエストのテスト
以下のコンポーネントは外部APIにHTTPリクエストを行い、取得したデータをpostsとしてレンダリングしています
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 |
<template> <div> <button @click="getPost">GetPost</button> <div class="post" v-for="post in posts" :key="post.id"> {{post.title}} </div> </div> </template> <script> import axios from 'axios'; export default { name: 'HttpComponent', data() { return { posts: [] } }, methods: { async getPost() { this.posts = await axios.get('/api/post') } } } </script> |
HTTPリクエストをテストするにはjest.mock関数でモジュールをモックします
下記の例ではaxiosモジュールのモックを作成し、get関数ではダミーデータを返却します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { mount, flushPromises } from '@vue/test-utils' import HttpComponent from '@/components/HttpComponent'; jest.mock('axios', () => ({ get: jest.fn(() => [ { id: 1, title: 'title1' }, { id: 2, title: 'title2' } ]) })) test('http test', async () => { const wrapper = mount(HttpComponent) await wrapper.get('button').trigger('click') await flushPromises() const posts = wrapper.findAll('div.post') expect(posts[0].text()).toBe('title1') expect(posts[1].text()).toBe('title2') }) |
テストコードの実行中はaxiosは常にモックが呼ばれるので、例えばVuexのactionで呼ばれていたとしてもモックが呼ばれることになります
まとめ
以上がVueJSのユニットテストのまとめになります。
基本的にはWrapperを使ってコンポーネントを操作し、Jestで検証するといった形になります。
ユニットテストは必要性を感じつつも導入が難しそう、テストコードを書くのが難しそうといった理由で後回しになりがちですが、導入はVueCLIを使っていればかなり簡単で、テストコードに関してもHTTPリクエストやVuexを使っているコンポーネントのテストは難しそうなイメージがありましたが、テストツールでサポートされているので特に難しいことはありませんでした。
参考
- VueTestUtils
- Jest