Nuxt.js の Plugin 内で Vuex Action を実行する / inject する / inject したものの型定義する / Jest でうまく Mock して Action のテストをする、、、そのへんの覚書

📆June 18, 2020

タイトルをうまくまとめるのを諦めた男。

僕たちのサービスでは、 Axios のラッパークラスを Plugin 内に定義し、VuexAction 内で DI して使うという設計を行っています。この設計に加え、以下の2つのルールをルールとして開発を進めています。

  • View 層では API リクエストは行わず、API リクエストは必ず Action から行う
  • Action は必ず Jest を用いてテストを書く

今回は、API リクエストで、何かしらのエラーが発生したときに、トーストを表示させるという機能追加を行いました。Plugin 内で Action を呼ぶ方法や、inject した関数をどうやってモックするかについてうまくまとまったドキュメントがなかったので、自分用の覚書も兼ねて書き残しておきたいと思います。

API リクエストに失敗した場合のトースト表示
API リクエストに失敗した場合のトースト表示

🤘 Plugin 内で Vuex Action を実行する / Inject する

以下のコードのように、Pluginexport default した関数は、引数として contextinject を受け取ります。

// @/plugins/ApiClient.ts

export default (context: Context, inject: Inject) => {
  // context.store で Store にアクセス可能(アクションを実行する)
  // 僕たちの場合は、ここで、エラーが発生した場合のアクションを呼ぶ関数を定義している。

  inject('apiClient', ApiClient.create());
};

第一引数の context オブジェクトには store の情報が詰まっているので、ここから Actiondispatch してやることで、指定したアクションを実行することができます。僕たちは、エラーハンドラーを定義し、エラーが発生したときに、トーストを表示させるアクションを共通処理として挟むことにしました。

第二引数の inject 関数は、第一引数で指定した命名が、 .vue コンポーネント内や、store 内で this.$XXX として扱えるようになります。$ が自動的に付くことに注意です。今回は、axios ラッパーに apiClient という名前を付けています。

こんな感じでゴニョゴニョ初期設定してやることで、Store 内で this.$apiClient.get(XXX) みたいな感じで、リクエストを飛ばすことができるようになります。エラーが置きた場合は、ApiClient 側の共通処理でうまくハンドリングするようにしています。

🤘 Vuex で Inject したものを TypeScript でも扱えるように型定義する

このままだと、this.$apiClient.get(XXX) でアクセスした際に、this$apiClient なんて存在しないよ!と TypeScript のコンパイラに怒られてしまうので、this$apiClient が存在することを TypeScript に教えてやる必要があります。

この this がコンテキストによって異なるのが JavaScript の悪いところなのですが、今回は Action 内での this にスコープを絞って型定義を書いていきます。結論から言うと、以下を書いておけば大丈夫です。

declare module 'vuex/types' {
  interface Store<S> {
    readonly $apiClient: ApiClient;
  }
}

🤘 Jest で Action のテストを書く

テストは以下のようなイメージ。

// モック用の apiClient を作って import 
import {apiClientForMock} from '@/XXX/ApiClientForMock'

describe('XXX', () => {
  let store: any;

  beforeEach(() => {
    store = new Vuex.Store(cloneDeep(calendarEvent));
    store.$apiClient = apiClientForMock; // 手動で、モック用の ApiClient をinject する
  });

  it('XXX', async () => {
    await store.dispatch('XXX');
    // テストを書く
}

テストはこんな感じで、store にモック用の apiClientimport してテストを行います。Jest のモック機能だと inject がうまく扱えなかったので、こんな感じで手動で inject しています。


以上、いい感じにテストする方法でした。なかなか良くまとまっているドキュメントがなかったので結構ハマった。



🔖 NuxtJSJavaScriptTypeScript