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


この記事は[PORT Advent Calender 2017](https://qiita.com/advent-calendar/2017/port) 22日目の記事です。

# 前書き
私の担当するサービスでは、JSフレームワークにVue.jsを使用しています。
※弊社の他プロダクトではReactやjQueryももちろんあります。(人数的にはそっちの方が多いですね・・)

今回は、以前めでたくVue.js公式のテストライブラリとして誕生した[vue-test-utils](https://github.com/vuejs/vue-test-utils)を触ってみようと思います。
ちなみに、私のテスト歴は、

- CasperJsでちょっとE2Eテスト書いてみたことある
- vue-test-utilsの前身である[avoriaz](https://github.com/eddyerburgh/avoriaz)ちょっとだけ触ってみた


くらいなので、テストは初心者です。
なので、初心者がこんなとこではまったよ！レポートです。

# やりたいこと
以前作ったVuex勉強用の[サンプル](https://qiita.com/_P0cChi_/items/ebf8fbf035b36218a37e)に対して、テストというものを書いてみたい。

# 準備
準備に関しては、[公式ドキュメントのこのページ](https://vue-test-utils.vuejs.org/ja/guides/testing-SFCs-with-jest.html)を参考にしました。
サンプルプロジェクトもありますし、既存のvue-cliを使用した環境へのセットアップは結構すんなりできたと思います。
MochaではなくJestの選択になったのは、単純にそのあとに続く[Vuexと一緒に使う](https://vue-test-utils.vuejs.org/ja/guides/using-with-vuex.html)のサンプルが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())
  })
})

```
<img width="526" alt="スクリーンショット 2017-12-22 2.35.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/48488/18a851ab-3928-94fe-9f12-67203a2648b8.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.Store`に`modules`を登録してやればいいみたいです。
ここまで分かれば、普通に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のテスト方法をまだ見てないですが・・。