Vue.js+Vuex環境のJestを用いたテストのやり方 (Component編:序)

# :beginner: はじめに
Vue.jsとVuexを中心にしたフロントエンド環境はNuxt.jsにも採用され、Qiitaでは完全に2018年の覇権といって良い構成だと思います:raising_hand:

一方でReactのような初学者に厳しいフレームワークと比べ、Vue.jsはテストに関する記事が少なく感じるため私のテスト手法を紹介します。
※関係ないですがvue公式のチュートリアルとrailsチュートリアルを見比べると、そこかしこに時代の流れが感じられて風流:joy:

読みやすさで話題のVueやNuxt謹製の日本語公式サイトですが、テストに関しては解説が充実してません。
[vue-test-utils公式](https://vue-test-utils.vuejs.org/ja/)を参照するのがオススメです。

今回は紹介しませんが、Jest以外のテストランナーやTypeScriptでのテスト手法も解説されています。

## :wrench: 環境構築
主要な利用ツールは以下です。
- Vue.js
- Vuex
- Jest
- vue-test-utils
- babelとかwebpackとかその辺

実際の環境構築は以下を参考にしてください。
[Jest を使用した単一ファイルコンポーネントのテスト](https://vue-test-utils.vuejs.org/ja/guides/#jest-%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E5%8D%98%E4%B8%80%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88)
[上記のgithubリポジトリ](https://github.com/vuejs/vue-test-utils-jest-example)

## :scroll: テストの概要
下記２つの項目に分けて、テストを行います。

・Component = Vue.jsを中心としたテスト
・Store = Vuexを中心としたテスト

例えばComponentのテストを行う際Storeの生成は行いますが、
Storeの持ってるActionsはStoreにてテストするためComponentではモックします。

本記事ではComponentを対象としたテストを紹介します。
※[Vueコンポーネントとは？](https://jp.vuejs.org/v2/guide/components.html)

Store編は以下
[Vue.js+Vuex環境のJestを用いたテストの書き方 (Store編:破急)](https://qiita.com/suzu-4/items/cf4f52049c681f4889e5)

## :scroll: テスト手法
効率的なテスト手法については様々な意見があると思いますが、
個人的にフロントエンドではTDDや関数単位のユニットテストと広く呼ばれているテストは行いません。

以下のgoogle謹製ブログで紹介されているmediumサイズのテストに近づくように意識しています。
https://testing.googleblog.com/2010/12/test-sizes.html

Largeサイズテスト=E2Eテストについては本記事では紹介しません。puppeteerをご検討ください。
また、状態ごとのクロスブラウザUI検証も対象外です。storybookをご検討ください。

今回テストするのは、様々な状態において各コンポーネントが、

・どのようなDOMを所持しているか
・どのようなスタイルを所持しているか
・どのようなインタラクションを所持しているか
※どのようなイベントが発生し、どのようにコンポーネント内データが更新されたりStoreのActionsが呼ばれるか

といった内容です。

# :memo: テスト対象
今回はVuex公式にて実装例として紹介されている、TODO機能に対してテストコードを記述します。
https://github.com/vuejs/vuex/tree/dev/examples/todomvc
上記の component/TodoItem.vue に対してテストを行います。

# :pencil: テストコード
コードを介して解説を行います。コードの意図に関しては直前で適宜コメントしています。
全体のテストは行っておりません。雰囲気つかめる程度の要所要所なテストです。

```js:test/components/TodoItem.spec.js
import { shallowMount, createLocalVue } from '@vue/test-utils'
import TodoItem from '~/components/TodoItem.vue'
import Vuex from 'vuex'

const localVue = createLocalVue()
localVue.use(Vuex)

// App.vueを親として、storeのstateをフィルタリングしたデータがpropsとして渡されてくる。
// propsをモックするための関数を定義。
const getPropsMock = () => ({
  todo: {
    text: 'foo',
    done: false
  }
})

describe('components/TodoItem.vue', () => {
  let actions
  let store
  let wrapper

  beforeEach(() => {
    // storeが所持するactionsはモックする。
    // jest.fn()を通してモックしておくことで、後ほど正しい引数がdispatchされてることを確認出来る。
    actions = {
      editTodo: jest.fn(),
      toggleTodo: jest.fn(),
      removeTodo: jest.fn()
    }
    // jest環境下で実行するためのｓtoreを生成しておく。実際の開発ではほぼmodulesモードで利用すると思うが。
    store = new Vuex.Store({
      state: {},
      actions
    })
  })

  // componentはprops(状態)によって表示が切り替わるので、propsをパターン分けしmountしていく。
  describe('has todo props', () => {
    describe('todo is not done', () => {
      beforeEach(() => {
        wrapper = shallowMount(TodoItem, {
          propsData: getPropsMock(),
          store,
          localVue
        })
      })

      // actionsをテストする例
      it('toggle input', () => {
        // このへんの説明は、https://vue-test-utils.vuejs.org/ja/api/wrapper/
        const input = wrapper.find('.toggle')
        input.trigger('click')

        // ここハマりどころですがdispatchの引数は、contextとpayloadだけではなく3つ目があります
        expect(actions.toggleTodo).toHaveBeenCalledWith(
          expect.anything(),
          getPropsMock(),
          expect.anything()
        )
      })

      // snapshotは各状態でとっておくことをオススメします。DOMのチェックになります。
      it('match snapshot', () => {
        expect(wrapper.html()).toMatchSnapshot()
      })

      ...
    })

    // 別の状態をmountする例です
    describe('todo is done', () => {
      beforeEach(() => {
        wrapper = shallowMount(TodoItem, {
          propsData: {
            ...getPropsMock(),
            todo: true
          },
          store,
          localVue
        })
      })

      // styleをテストする例
      it('todo has completed', () => {
        const todo = wrapper.find('.todo')
        expect(todo.classes)..toContain('completed')
      })

      ...
    })
  })
})

```

# :japanese_goblin: Componentテストのまとめ
・関数のユニットテストは記載しない。
・ComponentはStateやPropsに基づいて、computedやfilterやmethodやらが入り乱れ、各要素がレンダリングされたり、インタラクションが生まれるはずなので、それら**状態**のテストを行う。
・Storeにdispatchする場合は、actionsをモックしpayloadの引数を検証する。

なるべく小さくシンプルなテストを書くことを意識すると、コンポーネントを分割する際propsに余計なデータを含ませず、
美しいインタフェースのコンポーネントを実装していく訓練になって良かったです☺️

Store編にてVue.js+Vuex環境でのテストまとめを記載しておりますので、よろしければそちらも！
[Vue.js+Vuex環境のJestを用いたテストの書き方 (Store編:破急)](https://qiita.com/suzu-4/items/cf4f52049c681f4889e5)