vue.js
Vuex
vue-test-utils
PORTDay 22

vue-test-utilsでvuexモジュールのテストを書く

この記事は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のテストです。

src/component/modules/HeadComp.vue
<template>
  <h1>{{title}}</h1>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'headComp',
  computed: mapGetters({
  'title': 'getTitle'
  })
}
</script>

このコンポーネントに対して、以下のように作成しました。

test/HeadComp.spec.js
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のテストが書けない

問題だったのがコイツ

src/component/modules/StringComp.vue
<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を切ってあるものに関して、以下のように書くと、

test/StringComp.spec.js(失敗します)
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())
  })
})

スクリーンショット 2017-12-22 2.35.58.png

残念ながら失敗します。
[vuex] module namespace not found in mapGetters(): String/
と出ているのは、namespaceがStringのモジュールが見つけられない。
そのために、Receivedに何も入ってこないようです。

これを回避するためには、Stringモジュールを作成する必要があります。↓

test/StringComp.spec.js(成功します)
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.Storemodulesを登録してやればいいみたいです。
ここまで分かれば、普通にVuexを書くのと変わらない感覚で書けると思います。

ちなみに、公式ドキュメントにあったモジュールを利用するテストに沿って、以下のgetterテストも作成してみました。

test/StringComp.spec.js(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のテストを書く

src/component/Form.vue
<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のところだけです。

test/Form.spec.js
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のテスト方法をまだ見てないですが・・。