トップ/記事一覧

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

📆2020/06/18(最終更新日:2020/06/17)🔖 NuxtJSJavaScriptTypeScript

この記事は最終更新日から1年以上が経過しています。内容が古い箇所がある可能性があるためご注意ください。

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

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

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

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

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

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

    JavaScript

    // @/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 にスコープを絞って型定義を書いていきます。結論から言うと、以下を書いておけば大丈夫です。

    JavaScript

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

    🤘 Jest で Action のテストを書く

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

    JavaScript

    // モック用の 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 しています。

    🐈

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