前置き: Vuexストアの名前空間について
Vuexのstoreにおいて、ファイルが肥大化するのを防ぐためにモジュールを名前空間で分けることがあります。
その際、storeファイルで別モジュールのファイルを呼び出すときは「モジュール名/アクション名」で書くことができます。
(Nuxt.jsのモジュールモードで書いた例です。Vuexのみの場合は書き方が違う可能性があります。)
例: store/index.js
export const actions = {
async clientLogin({ commit, dispatch }, params) {
try {
//ログイン処理
} catch (error) {
//ログイン失敗時
if (error.response.status == "401") {
dispatch(
//store/index.js内で、store/presentations内のshowAndCloseMessageアクションを呼び出す
"presentations/showAndCloseMessage",
{
message: "IDもしくはパスワードに誤りがあります",
isSuccessMessage: false
},
{ root: true }
);
} else {
throw new Error(error.response.status);
}
}
}
};
{ root: true }
この部分はグローバル名前空間にアクションをコミットするために必要です。
"presentations/showAndCloseMessage"はrootが起点となるので、dispatchの第3引数として{ root: true }が必要となります。
ここまでは公式ドキュメントにもある記述ですが、この処理をJestでテストする際にめちゃめちゃハマりました。
本題: テスト中での別モジュールのaction呼び出し
今回別モジュールのactionを呼び出すのは、indexストアのログイン処理の中です。
store/index.js
export const state = () => ({
token: null,
ID: null
});
export const getters = {
token: state => state.token,
ID: state => state.ID
};
export const mutations = {
setClient(state, { ID, token }) {
state.ID = ID;
state.token = token;
}
};
export const actions = {
async clientLogin({ commit, dispatch }, params) {
try {
//ログイン処理
} catch (error) {
if (error.response.status == "401") {
//失敗時は別モジュールのactionを呼び出して、メッセージを表示する
dispatch(
//ここ!!
"presentations/showAndCloseMessage",
{
message: "IDもしくはパスワードに誤りがあります"
},
{ root: true }
);
} else {
throw new Error(error.response.status);
}
}
}
};
この部分は、認証エラーでログイン失敗したときの処理です。
別モジュールであるpresentationsストアのshowAndCloseMessageアクションを呼び出して、フラッシュメッセージを一定時間表示するようにしています。
では実際にこの別モジュールのストアのstateの更新と、その確認をどう行ったらよいでしょうか?
下記のように書くことで別モジュールのアクションの発行とテストを行うことができました。
①〜⑤を順番に解説します。
stores/index.spec.js
import { createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
//①使用するstoreをimportしておく
import * as index from "@/store/index.js";
import * as presentationsStore from "@/store/presentations.js";
import { cloneDeep } from "lodash";
import axios from "axios";
const MockAdapter = require("axios-mock-adapter");
const mock = new MockAdapter(axios);
const localVue = createLocalVue();
localVue.use(Vuex);
//②ストアの汚染を防ぐためにcloneDeepでstoreをコピーする
const store = new Vuex.Store(cloneDeep(index));
const presentation_store = new Vuex.Store(cloneDeep(presentationsStore));
store.$axios = axios;
//③storeをモックする
let presentations = {
namespaced: true,
actions: {
showAndCloseMessage: ({}, arg) =>
// actions内で他モジュールのactionをdispatchしている部分
// "presentations/showAndCloseMessage"がdispatchされた時に内部でstore/presentations内のactionを呼ぶ
presentation_store.dispatch("showAndCloseMessage", {
message: arg.message
})
},
state: {
message: () => ""
}
};
describe("クライアントアカウントでのログイン(clientLogin())", () => {
//④store/index.jsからモックが呼び出せるようにします
store.registerModule("presentations", cloneDeep(presentations));
const loginClient = { id: "sample", pass: "TestUser0000" };
it("clientLogin()の401エラー時にメッセージを表示する", async () => {
mock.onPost("/api/client/login").reply(401);
await store.dispatch("clientLogin", loginClient);
//⑤store/presentationsでstateが更新されていることを確認
expect(presentation_store.state.message).toBe(
"IDもしくはパスワードに誤りがあります"
);
});
});
解説
①使用するstoreをモジュール名でimport
indexストアはストア名を使い、presentationsストアは分かりやすい別名にしておきます
②storeのコピー
ストアの汚染を防ぐためにcloneDeepでstoreをコピーします。
ここでもstoreの変数名は"store"にして、presentationsストアのコピーは"presentation_store"としておきました。
参考: 実行可能なストアのテスト
③storeのモック
コピーした、presentationsストアをモックします。
ここでの変数名はindexストア内でdispatchされるモジュール名に合わせましょう。
ここからがポイントです
showAndCloseMessage: ({}, arg) =>
// actions内で他モジュールのactionをdispatchしている部分
// "presentations/showAndCloseMessage"がdispatchされた時に内部でstore/presentations内のactionを呼ぶ
presentation_store.dispatch("showAndCloseMessage", {
message: arg.message,
isSuccessMessage: arg.isSuccessMessage
})
モック内のshowAndCloseMessage内では、ダミーのアクション(jest.fn())ではなく、先ほどimportしてきたstore内のshowAndCloseMessageを実際にdispatchします。
これにより、モックでactionが呼ばれたかどうかだけでなく、その後stateが正しく更新されたかどうかまで確認できるようになりました。
④モックをテスト対象のstoreに組み込む
これでpresentationストアのモックからactionの呼び出しができるようになりましたが、これがindex.jsのコピーであるstoreから呼べなければ意味がありません。
そこでstoreに、presentationsストアのモックをpresentationsモジュールとして登録します。
store.registerModuleメソッドを使えば、storeを作成した後にモジュールが登録できるのでこれを使います
⑤認証エラー時にpresetationsストアのstateが更新されていることを確認
mock.onPost("/api/client/login").reply(401);
この部分で、axiosをモックして認証エラーを返すようにしています。
この状態でテストをすると、フラッシュメッセージが表示されるはずです。
expect(presentation_store.state.message).toBe(
"IDもしくはパスワードに誤りがあります"
);
実際に、上記のテストでpresentationsストアのmessageの値を確認すると、正しいmessageに更新されていることが確認できました。
presentationsストアのstateは、コピーしてきたpresentation_storeのstateから確認できます。
長々と書きましたが、いろいろなドキュメントやサイトを見ながら書いたので、もっと良い書き方があればいつでもマサカリお待ちしています。