前回:
Vue.js+Vuexでasync/awaitを使ったPresentational and Container Component Pattern(サンプルつき)
前回は、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を使用します。
コンポーネント内のテストのポイントは、
- sharrowMount(またはMount)を使って、対象のコンポーネントだけをマウントする
- コンポーネント内でテストをする範囲は「DOMを操作して、特定のActionが実行されたか」までとする
- ActionによってStoreの中身が正しく変化したかどうかは、Storeのテストで確認する
実務でも、コンポーネント内の関数はDOM操作と紐づいた処理が多いため、このような方法で確認をするのがいいかと思います。
スナップショットテストのポイントは、
- マウントしたHTMLとスナップショットを比較する形で行う
となります。
このテストで何を保証するか
- コンポーネント内のボタンをクリックすると、Actionが実行される
- 前回のHTMLのスナップショットと比較し、対象のDOMツリーが変更されていない
ユニットテストの実装例
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が変更されるか
ユニットテストの実装例
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を使った環境でも、分かりやすい形で自動テストを実装することができます。
ユニットテストを導入することで、コンポーネントを実装する際に、どうすればテスト可能な単位となるかを考えるようになるため、積極的に導入していきたいです。