この記事はPORT Advent Calender 2017 22日目の記事です。
前書き
私の担当するサービスでは、JSフレームワークにVue.jsを使用しています。
※弊社の他プロダクトではReactやjQueryももちろんあります。(人数的にはそっちの方が多いですね・・)
今回は、以前めでたくVue.js公式のテストライブラリとして誕生したvue-test-utilsを触ってみようと思います。
ちなみに、私のテスト歴は、
- CasperJsでちょっとE2Eテスト書いてみたことある
- vue-test-utilsの前身であるavoriazちょっとだけ触ってみた
くらいなので、テストは初心者です。
なので、初心者がこんなとこではまったよ!レポートです。
やりたいこと
以前作ったVuex勉強用のサンプルに対して、テストというものを書いてみたい。
準備
準備に関しては、公式ドキュメントのこのページを参考にしました。
サンプルプロジェクトもありますし、既存のvue-cliを使用した環境へのセットアップは結構すんなりできたと思います。
MochaではなくJestの選択になったのは、単純にそのあとに続くVuexと一緒に使うのサンプルがJestだったからです。
私は、src
ディレクトリと同階層上にtest
ディレクトリを切りました。
Getterのテストを書く
一番簡単そうなgetter
のテストです。
<template>
<h1>{{title}}</h1>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'headComp',
computed: mapGetters({
'title': 'getTitle'
})
}
</script>
このコンポーネントに対して、以下のように作成しました。
import { shallow, createLocalVue } from 'vue-test-utils'
import Vuex from 'vuex'
import Actions from '@/components/modules/HeadComp'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('HeadComp.vue', () => {
let getters
let store
beforeEach(() => {
getters = {
getTitle: () => 'タイトルだよ',
}
store = new Vuex.Store({
getters
})
})
it('h1タイトルの表示', () => {
const wrapper = shallow(Actions, { store, localVue })
const h1 = wrapper.find('h1')
expect(h1.text()).toBe(getters.getTitle())
})
})
公式ドキュメントに沿って書きました。問題なく動きました。
getTitle
というgetterをモック化して、
expect(h1.text()).toBe(getters.getTitle())
でh1で表示されるテキスト
とgetters.getTitleの値
を比較しています。
問題はここからです・・・\(´・∀・`)/
Getterのテストが書けない
問題だったのがコイツ
<template>
<p>{{string}}</p>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'stringComp',
computed: mapGetters('String', {
'string': 'getString'
})
}
</script>
ここです。↓
computed: mapGetters('String', {
'string': 'getString'
})
namespaceを切ってあるものに関して、以下のように書くと、
import { shallow, createLocalVue } from 'vue-test-utils'
import Vuex from 'vuex'
import Actions from '@/components/modules/StringComp'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('StringComp.vue', () => {
let getters
let store
beforeEach(() => {
getters = {
getString: () => '文字列だよ',
}
store = new Vuex.Store({
getters
})
})
it('p文字列の表示', () => {
const wrapper = shallow(Actions, { store, localVue })
const p = wrapper.find('p')
expect(p.text()).toBe(getters.getString())
})
})
残念ながら失敗します。
[vuex] module namespace not found in mapGetters(): String/
と出ているのは、namespaceがString
のモジュールが見つけられない。
そのために、Received
に何も入ってこないようです。
これを回避するためには、String
モジュールを作成する必要があります。↓
import { shallow, createLocalVue } from 'vue-test-utils'
import Vuex from 'vuex'
import Actions from '@/components/modules/StringComp'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('StringComp.vue', () => {
let String
let store
beforeEach(() => {
String = {
namespaced: true,
getters: {
getString: () => '文字列だよ',
}
}
store = new Vuex.Store({
modules: {
String
}
})
})
it('p文字列の表示', () => {
const wrapper = shallow(Actions, { store, localVue })
const p = wrapper.find('p')
expect(p.text()).toBe(String.getters.getString())
})
})
Vuexを使用するときのように、
new Vuex.Store
にmodules
を登録してやればいいみたいです。
ここまで分かれば、普通にVuexを書くのと変わらない感覚で書けると思います。
ちなみに、公式ドキュメントにあったモジュールを利用するテストに沿って、以下のgetterテストも作成してみました。
import { shallow, createLocalVue } from 'vue-test-utils'
import Vuex from 'vuex'
import Actions from '@/components/modules/StringComp'
import String from '@/store/modules/String'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('StringComp.vue', () => {
let store
let state
beforeEach(() => {
state = {
impression: "文字列だよ"
}
store = new Vuex.Store({
state,
modules: {
String
}
})
})
it('p文字列の表示', () => {
const wrapper = shallow(Actions, { store, localVue })
const p = wrapper.find('p')
expect(p.text()).toBe(state.impression.toString())
})
})
これだと、実際に使用しているstoreモジュールのgetterを使用することができます。
Actionのテストを書く
<template>
<div>
Formページ
<HeadComp></HeadComp>
<component
:is="isComponent"
></component>
<button v-on:click="buttonAction">{{button}}</button>
</div>
</template>
<script>
import HeadComp from '@/components/modules/HeadComp'
import TextareaComp from '@/components/modules/TextareaComp'
import StringComp from '@/components/modules/StringComp'
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'form',
methods: mapActions('Form', {
'buttonAction': 'buttonAction'
}),
computed: mapGetters('Form', {
'button': 'getButton',
'isComponent': 'getComponent'
}),
components: {
HeadComp,
TextareaComp,
StringComp
}
}
ちょっと複雑そうなコンポーネントですが、
書いたのはひとまずbuttonのところだけです。
import { shallow, createLocalVue } from 'vue-test-utils'
import Vuex from 'vuex'
import Actions from '@/components/Form'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('Form.vue', () => {
let Form
let store
beforeEach(() => {
Form = {
namespaced: true,
actions: {
buttonAction: jest.fn(),
},
getters: {
getComponent: () => '',
getButton: () => ''
}
}
store = new Vuex.Store({
modules: {
Form
}
})
})
it('ボタンクリック', () => {
const wrapper = shallow(Actions, { store, localVue })
const button = wrapper.find('button')
button.trigger('click')
expect(Form.actions.buttonAction).toHaveBeenCalled()
})
})
こちらもnamespaceが切ってあるので、Form
モジュールを作成します。
ボタンをクリックして、buttonAction
が呼ばれるかどうかだけのテストです。
getterに関してはテストは書いていませんが、例のエラー
[vuex] module namespace not found in mapGetters(): Form/
が出てしまうため、モックだけ書いておきました。
やってみて
今回は手始めなので、これくらいで勘弁しました。
なんとなく感覚は掴めたので、ちまちま書いていけそうです。
mutetionsのテスト方法をまだ見てないですが・・。