概要

VueRouter のナビゲーションガードのテストの書き方について。

  1. グローバルガード
  2. コンポーネント内ガード

それぞれでの書き方を紹介します。

グローバルガード

以下のような beforeEach フックを定義した router インスタンスがあるとします。

// router.js
// ...
router.beforeEach((to, next, from) => {
  authMiddleware(to, next, from);
});

beforeEach などのフックは router インスタンスの中ですべて beforeHooks という場所へ登録されるようになっています。
登録されているフックを実行したい場合、 beforeHooksforEach で呼び出します。

import router from "@/router";
import { authMiddleware } from "@/auth";

jest.mock("@/auth");
authMiddleware.mockImplementation(jest.fn());

describe("router.js", () => {
  test("beforeEach", () => {
    const next = jest.fn();
    router.beforeHooks.forEach((hook) => {
      hook(undefined, undefined, next);
    });

    expect(authMiddleware).toHaveBeenCalled();
  });
});

この方法でもテストは実行できます。
しかし、beforeHooks に登録されているすべてのフックを呼び出してしまうという欠点があります。
フックを個別に呼び出してテストを実行したい場合は、フック自体をモジュールとして抽出してやると良いでしょう。

// router.js
// ...
export function beforeEach(to, from, next) {
  authMiddleware(to, from, next);
}

router.beforeEach((to, from, next) => beforeEach(to, from, next));

この場合のテストは以下のようになります。
モジュールとして抽出したおかげで router インスタンスを介さずにテストが実行できました。

// beforeEach.spec.js
import { beforeEach } from "@/router";
import { authMiddleware } from "@/auth";

jest.mock("@/auth");
authMiddleware.mockImplementation(jest.fn());

describe("beforeEach hook", () => {
  test("call authMiddleware function", () => {
    const next = jest.fn();
    beforeEach(undefined, undefined, next);

    expect(authMiddleware).toHaveBeenCalled();
  });
});

コンポーネント内ガード

コンポーネント内ガードの場合、特に難しいことはありません。
import したコンポーネントから call() 関数を使ってテスト対象を呼び出すだけです。

// HelloWorld.vue
<script>
import { authMiddleware } from "@/auth";

export default {
  name: "HelloWorld",
  beforeRouteEnter(to, from, next) {
    authMiddleware(to, from, next);
  },
};
</script>
// HelloWorld.spec.js
import { shallowMount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";

import { authMiddleware } from "@/auth";
jest.mock("@/auth");
authMiddleware.mockImplementation(jest.fn(() => true));

describe("HelloWorld.vue", () => {
  test("beforeRouteEnter", () => {
    const wrapper = shallowMount(HelloWorld);
    HelloWorld.beforeRouteEnter.call(
      wrapper.vm,
      undefined,
      undefined,
      undefined
    );

    expect(authMiddleware).toHaveBeenCalled();
  });
});