Vuex内のaxiosをモックしてテストする

こんにちは、アイスタイルのVue.jsマン(自称)ことkubotakです。
今回はVue.jsにおける、Vuexのactionsでaxiosによる非同期通信取得をしている場合のテストと、クライアントのテストに関して紹介したいと思います。
(若干私的な備忘録も兼ねてます)
テストコードに関しての説明となるのでjestaxiosについては割愛します。

前提条件

  • vue-cliにより作成したプロジェクト
  • jestでのunit test
  • ajax通信はaxiosで行なっている
  • axiosクライアントをラップしたクライアントモジュールを用意する

クライアントのテスト

axiosクライアントをラップしたクライアントモジュールの実装としては以下のような感じです。

import axios from "axios";

export const client = axios.create({
  baseURL: "https://example.test",
  timeout: 5000
});

export const getHoge = async function(id) {
  let res = await client.get(`/hoge/${id}`);
  return res.data;
}

clientをexportしているのはテストでモックさせるためです。
このクライアントモジュールをテストするにあたり、以下のテストライブラリを導入します。

それではテストを書いてみます。

import MockAdapter from "axios-mock-adapter";
import { client, getHoge } from "@/api/client";
const mockAxios = new MockAdapter(client);

describe("api client", () => {
  it("getHoge should return data", () => {
    let id = 1;
    let result = {
      id: id,
      name: "hoge"
    };
    mockAxios.onGet(`/hoge/${id}`).reply(200, result);
    return getHoge(id).then(res => {
      expect(res).toEqual(result);
    });
  });
});

getHogeのPromiseから指定した値が返ってくることが確認できました。

Vuexで利用しているクライアントをモックする

先ほど作成したクライアントモジュールをVuexで利用してみます。

import { getHoge } from "@/api/client";

export default {
  namespaced: true,
  state: () => ({
    id: null,
    name: ""
  }),
  mutations: {
    setData(state, { id, name }) {
      state.id = id;
      state.name = name;
    }
  },
  actions: {
    getHoge({ commit }, { id }) {
      return getHoge(id).then(res => {
        commit("setData", res);
        return Promise.resolve(res);
      });
    }
  }
}

actionsのメソッド、 getHogeで同名のクライアントモジュールのメソッドを実行して、ajaxで値が取得できたらstateを更新するというよくある形かと思います。今回はこのクライアントモジュールの getHoge をモックしたテストを書いてみます。

import { cloneDeep } from "lodash";
import { default as hogeModule } from "@/store/modules/hoge";

jest.mock("@/api/client", () => ({
  getHoge: jest.fn(() =>
    Promise.resolve(require("./stub/hoge.stub.json"))
  )
}));

describe("hoge store test", () => {
  let store;
  let commit;

  beforeEach(done => {
    store = cloneDeep(hogeModule);

    commit = (type, payload) => {
      store.mutations[type](store.state, payload);
    };
    store.commit = commit;

    done();
  });

  it("default state", done => {
    expect(store.state().id).toBe(null);
    expect(store.state().name).toBe("");
    done();
  });

  it("after getHoge should be set store", done => {
    let id = 1234;
    store.actions.getHoge({ commit }, { id }).then(() => {
      expect(store.state().id).toBe(1234);
      expect(store.state().name).toBe("test");
      done();
    });
  });
});

VuexのActionメソッドを実行することでaxiosによるXHRでのAPIレスポンス取得から、Vuexのstateに値をセットすることをテストできました。
一つ一つ処理を見て見ます。

jest.mock("@/api/client", () => ({
  getHoge: jest.fn(() =>
    Promise.resolve(require("./stub/hoge.stub.json"))
  )
}));

ここで、jest.mockの登場です。
@/api/clientをjest.mockによりクライアントをモック化することができます。
さらに getHogejest.fn() によりモックメソッド化し、このメソッドが呼ばれるとき、Promise.resolveで用意したテスト用のjsonを返す様になりました。

hoge.stub.jsonは以下の様にしています。

{
  "id": 1234,
  "name": "test" 
}

commitの実装も用意します。
ここではactionsでcommitを実行した場合に、mutationsに反映される様に準備しています。

beforeEach(done => {
  store = cloneDeep(hogeModule);

   commit = (type, payload) => {
    store.mutations[type](store.state, payload);
  };
  store.commit = commit;

  done();
});

最後にactionsが実行された場合に正しくaxiosから取得した値をstateに反映されることをテストします。

it("after getHoge should be set store", done => {
  let id = 1234;
  store.actions.getHoge({ commit }, { id }).then(() => {
    expect(store.state().id).toBe(1234);
    expect(store.state().name).toBe("test");
    done();
  });
 });

jest.mockを利用することで@/api/clientの振る舞いを変えることができる紹介でした。
PHPであればDIでテスト時だけ別の実装を注入することができますが、今回のケースではimportしている実体を上書きするという手法で似た様なことを実現しています。
参考になれば幸いです。

弊社アイスタイルでは、Vue.jsをメインに利用し、モダンなフロントエンド開発に向かって邁進しております。
Vue.js大好きエンジニアも募集しています!
we are hiring!

アイスタイルの緑髪担当 元デザイナーの中途4年目です PHPer, JSer 時々Gopher