Posted at

Vue.js+Vuexで作成したカウンターアプリのユニットテストを作成する


概要

Vue.js+Vuexを利用したカウンターアプリのテストクラスを作成します。テストツールはJestを使用。

この記事では、カウンターアプリのテストとして適切であるかという点よりも、Vue.js+Vuexのテストのサンプルとなるように意識して書いています。

これからテストを書こうと思っている方の参考になれば幸いです。


テスト対象

今回のテストの対象となるコンポーネントとカウンターモジュールです。


Counterコンポーネント

カウントされる数値とincrement!decrement!ボタンが表示されています。


Counter.vue

<template>

<div>
<p>{{ counter }}</p>
<button @click="increment">increment!</button>
<button @click="decrement">decrement!</button>
</div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Getter, Action, namespace } from "vuex-class";
import { CounterState } from "../types/counter";

const CounterGetter = namespace("CounterModule", Getter);
const CounterAction = namespace("CounterModule", Action);

@Component
export default class Counter extends Vue {
@CounterGetter
counter!: CounterState["counter"];

@CounterAction
increment!: () => void;
@CounterAction
decrement!: () => void;
}
</script>



Counterモジュール

VuexStoreをモジュールに分割し、CounterModuleを作成しました。

Actionは単にMutationをcommitするだけのものとなっていますが、Vuexのデータフローをわかりやすくするために、このような形にしています。


counter.ts

import Vue from "vue";

import Vuex, { GetterTree, MutationTree, ActionTree, Module } from "vuex";
import { CounterState } from "@/types/counter";
import { RootState } from "@/store";

Vue.use(Vuex);

const state: CounterState = {
counter: 0
};

const getters: GetterTree<CounterState, RootState> = {
counter: (state): CounterState["counter"] => {
return state.counter;
}
};

const mutations: MutationTree<CounterState> = {
increment: state => {
state.counter += 1;
},
decrement: state => {
state.counter -= 1;
}
};

const actions: ActionTree<CounterState, RootState> = {
increment: ({ commit }) => {
commit("increment");
},
decrement: ({ commit }) => {
commit("decrement");
}
};

export const CounterModule: Module<CounterState, RootState> = {
namespaced: true,
state,
mutations,
actions,
getters
};



テスト観点


  • VuexStore (CounterModule)

  • コンポーネント (Counterコンポーネント)

VuexStoreのテストは、getterやmutation、actionのロジック単体を検証します。

コンポーネントのテストは、コンポーネントがVuexStoreのstateの状態に基づいて正しく動作していてることを検証します。VuexStoreのmutationやactionの内部ロジックについては、VuexStoreのテストで検証されるので、コンポーネントのテストでは検証しません。


VuexStore(CounterModule)のテスト

まず、tests/unit/CounterModule.spec.tsを作成します。

拡張子に.spec.jsをつけることで、Jestでテストが実行されます。

以下についてテストを行います。


  • Getter

  • Mutation

  • Action


CounterModule.spec.ts

import { CounterState } from "@/types/counter";

import { CounterModule } from "@/store/modules/counter";

describe("CounterModule", () => {
describe("getters", () => {
it("should be able to get counter", () => {
const state: CounterState = {
counter: 1
};

const wrapper = (getters: any) => getters.counter(state);
const counter: CounterState = wrapper(CounterModule.getters);

expect(counter).toEqual(state.counter);
});
});

describe("mutations", () => {
let state: CounterState;

beforeEach(() => {
state = {
counter: 1
};
});

it("should be able to increment counter", () => {
const wrapper = (mutations: any) => mutations.increment(state);
wrapper(CounterModule.mutations);

expect(state.counter).toEqual(2);
});

it("should be able to decrement counter", () => {
const wrapper = (mutations: any) => mutations.decrement(state);
wrapper(CounterModule.mutations);

expect(state.counter).toEqual(0);
});
});

describe("actions", () => {
let state: CounterState;

beforeEach(() => {
state = {
counter: 1
};
});

it("should be able to commit increment", () => {
const commit = jest.fn();

const wrapper = (actions: any) => actions.increment({ commit });
wrapper(CounterModule.actions);

expect(commit).toHaveBeenCalledWith("increment");
});

it("should be able to commit decrement", () => {
const commit = jest.fn();

const wrapper = (actions: any) => actions.decrement({ commit });
wrapper(CounterModule.actions);

expect(commit).toHaveBeenCalledWith("decrement");
});
});
});



Getter

getterで取得した値がstateと一致していることを検証。

補足:getter、mutation、actionのテストに共通しているのですが、

以下のコードは、TypeScriptのエラーを回避するため、強引にany型にキャストしています。

const wrapper = (getters: any) => getters.counter(state);

const counter: CounterState = wrapper(CounterModule.getters);

以下は、テストは通過しますが、TypeScriptでエラーが表示されます。

CounterModule.actions.increment({ commit });


Mutation

beforeEach()でstateを初期化し、mutationを実行することでstateの値が変更されていることを検証。


Action

Storeのcommitをjest.fn()でモック化しています。

expect(commit).toHaveBeenCalledWith("increment");でactionがmutationをコミットしていることを検証。


コンポーネントのテスト

ここでは、以下の3点をテストを行います。


  • カウントの数値の表示

  • incrementボタン押下の際のActionの呼び出し

  • decrementボタン押下の際のActionの呼び出し

ライブラリにはvue-test-utilsを使用。


Cunter.spec.ts

import { shallowMount, createLocalVue } from "@vue/test-utils";

import Vuex from "vuex";
import Counter from "@/components/Counter.vue";
import { CounterModule } from "@/store/modules/counter";
import { CounterState } from "@/types/counter";

const localVue = createLocalVue();

localVue.use(Vuex);

describe("Counter.vue", () => {
let store: any;
let state: CounterState;
let actions: any;

beforeEach(() => {
state = {
counter: 1
};

actions = {
increment: jest.fn(),
decrement: jest.fn()
};

store = new Vuex.Store({
modules: {
CounterModule: {
namespaced: true,
state,
actions,
getters: CounterModule.getters
}
}
});
});

it('renders "state.counter" in first p tag', () => {
const wrapper = shallowMount(Counter, { store, localVue });
const p = wrapper.find("p");
expect(p.text()).toBe(state.counter.toString());
});

it('calls store action "increment" when increment button is clicked', () => {
const wrapper = shallowMount(Counter, { store, localVue });
const button = wrapper.findAll("button").at(0);
button.trigger("click");
expect(actions.increment).toHaveBeenCalled();
});

it('calls store action "decrement" when decrement button is clicked', () => {
const wrapper = shallowMount(Counter, { store, localVue });
const button = wrapper.findAll("button").at(1);

button.trigger("click");
expect(actions.decrement).toHaveBeenCalled();
});
});



テストのポイント

VuexStoreのモジュールのテストを可能にするため、CounterModuleを作成しています。

ActionはJestのモック関数jest.fn()を使用し、ボタンが押下された際に、actions.incrementまたはactions.decrementが呼び出されたことを検証しています。


参考記事

以下の記事を参考にしています。

Vue Test Utils

Vue testing handbook