async/awaitを使った処理をVue.js + Vuex + Jest でユニットテストする

前回：
[Vue.js+Vuexでasync/awaitを使ったPresentational and Container Component Pattern(サンプルつき)](https://qiita.com/shibe23/items/52ab65710678031b2ded)

前回は、Presentational and Container Component Patternを使って、async/awaitを使った簡易な会員登録フローを実装しました。

今回は、もう少し単純なコンポーネントを例として、Jestを使い、async/awaitを使用した処理を、ユニットテストする方法をまとめています。

テストする内容は下記のとおりです。

- コンポーネント内のMethodsのテスト
- Store内のテスト
- Snapshotテスト(前回テスト時のDOMと差分がないか)

# サンプルについて
コード全体はこちらからご覧ください。
https://github.com/public-shibe23/sandbox-vue-testable

ローカルで確認をする場合は、下記コマンドを実行してください。

```
npm install
npm run demo
```

# 動作確認環境
vue-cli：3.5.2
node.js : 8.11.4
npm: 5.6.0

# コンポーネント内のテスト&スナップショットテスト
[@vue/test-utils](https://github.com/vuejs/vue-test-utils)を使用します。

コンポーネント内のテストのポイントは、

- sharrowMount(またはMount)を使って、対象のコンポーネントだけをマウントする
- コンポーネント内でテストをする範囲は「DOMを操作して、特定のActionが実行されたか」までとする
- ActionによってStoreの中身が正しく変化したかどうかは、Storeのテストで確認する

実務でも、コンポーネント内の関数はDOM操作と紐づいた処理が多いため、このような方法で確認をするのがいいかと思います。

スナップショットテストのポイントは、

- マウントしたHTMLとスナップショットを比較する形で行う

となります。

## このテストで何を保証するか
- コンポーネント内のボタンをクリックすると、Actionが実行される
- 前回のHTMLのスナップショットと比較し、対象のDOMツリーが変更されていない

## ユニットテストの実装例

```js:tests\unit\ProductList.spec.js
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import Home from "@/views/Home.vue";
import ProductListModule from "@/store/ProductList";

const localVue = createLocalVue();

localVue.use(Vuex);

describe("ProductList.vue", () => {
  let actions;
  let state;
  let store;
  let wrapper;

  beforeEach(() => {
    state = {
      products: [
        {
          id: 1000,
          name: "T-shirts",
          price: 980,
          stock: 20
        }
      ]
    };

    actions = {
      FETCH_PRODUCTS: jest.fn()
    };

    store = new Vuex.Store({
      modules: {
        ProductList: {
          namespaced: true,
          state,
          actions,
        }
      }
    });
    wrapper = mount(Home, { store, localVue });
  });

  it("Clickボタンを押すと、actions.FETCH_PRODUCTSが呼ばれる", () => {
    const button = wrapper.find(".button");
    console.log(wrapper.text());
    button.trigger("click");
    expect(actions.FETCH_PRODUCTS).toHaveBeenCalled();
  });

  it("match snapshot", () => {
    expect(wrapper.html()).toMatchSnapshot();
  });
});
```
`beforeEach()`内のstate, actions, stateは、テスト用のダミーデータです。
Vuexのmodulesを使用している場合は、`namespaced: true`をつける必要があります。

関数単位で検証をするというよりは、DOM内のイベントを発生させて、対象の関数をチェックするイメージです。
今回は、テストするコンポーネントの子コンポーネントに、クリックするボタンがあるため、shallowMountではなく、mountを使用しています、

DOM操作を行うテストとしては、e2eテストもありますが、ユニットテストの場合、ブラウザを起動しない分、実行速度はこちらの方が早くなります。

一方で、JestのSnapshotはDOM要素をテキストデータとして保存し、差分を比較するため、実行時の挙動は実際のブラウザと完全に同一ではない、という懸念があります。
どちらを採用するかは、テストの方針によって使い分けるのがいいかと思います。

# Storeのテスト
Store内のデータに対して正しい値を取得できているか確認を行います。

## このテストで何を保証するか
- Actionsによって正しくStoreが変更されるか

## ユニットテストの実装例
```js:tests\unit\ProductListState.spec.js
import { createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import { mutations, actions } from "@/store/ProductList";
import { cloneDeep } from "lodash";

const state = {
  products: [
    {
      id: "",
      name: "",
      price: "",
      stock: ""
    }
  ]
};

const initStore = () => {
  return cloneDeep({
    modules: {
      ProductList: {
        namespaced: true,
        state,
        mutations,
        actions
      }
    }
  });
};

describe("ProductList.vue", () => {
  let store;

  beforeEach(() => {
    const localVue = createLocalVue();
    localVue.use(Vuex);

    store = new Vuex.Store(initStore());
  });

  it("商品一覧を取得できる", async () => {
    await store.dispatch("ProductList/FETCH_PRODUCTS");
    expect(store.state.ProductList.products[0]).toEqual({
      id: "1000",
      name: "T-shirts",
      price: 980,
      stock: 20
    });
  });
});
```

localVueを作成し、実際に使用しているVuexを適用します。
Action実行後、Store内のデータを比較し、テストケースの値と同じものか検証しています。


# まとめ
@vue/test-utilsを使うことで、Vue.jsとVuexを使った環境でも、分かりやすい形で自動テストを実装することができます。

ユニットテストを導入することで、コンポーネントを実装する際に、どうすればテスト可能な単位となるかを考えるようになるため、積極的に導入していきたいです。


参考：
https://qiita.com/suzu-4/items/cf4f52049c681f4889e5