株式会社HAMWORKS
🛠️

next-test-api-route-handler のアップデートによるエラー解決の備忘録

2024/05/21に公開

はじめに

この記事は Next.js のAPIテストに利用した next-test-api-route-handlerv3.2.0 から v4.0.7 にアップデートした際に発生したエラーの対処法の備忘録になります。

https://www.npmjs.com/package/next-test-api-route-handler

対象読者

  • Next.js を使っている人
    • Pages Router から App Router に移行する人
  • next-test-api-route-handler を利用してテストを書いている人
  • next-test-api-route-handlerv3 => v4 にアップデートしたい人

前提

この記事は最終的に以下の環境とパッケージで構成されています。
パッケージに関してはこれらの他にも色々と導入していますが割愛します。

環境

  • Node.js v18.20.2
  • Next.js v13.4.12

主なパッケージ

  • jest v29.7.0
  • jest-environment-jsdom v29.7.0
  • next-test-api-route-handler v4.0.7
  • msw v2.2.2

エラー発生から解決までの流れ

Next.js の API Routes をテストする際に、これまで next-test-api-route-handler というパッケージを利用していました。

しかし、Pages Router で利用していた API を App Router に移行するためにテストを書き始めたところ、いくつかの問題が発生しました。

その詳細を以下に記載していきます。

1. App Routerへの移行と問題の発覚

最初に、Pages Router で使用していた API を App Router に移行するためのテストを書き始めました。

先にPages Router で利用しているAPIのテストを書くことにより、移行前後で動作に差異が無いかを確認出来ると考えたからです。

しかし、 next-test-api-route-handler のバージョン 3.2.0 ではApp Routerのテストができないことがわかりました。

そこで、パッケージを最新(記事執筆時)の v4.0.7 にアップデートしました。

2. テストの失敗と原因の特定

アップデート後、既存のAPI Routesのテストが失敗し始めました。

調べた結果、Pages Router と App Router のどちらのテストをするかで記法が異なることが原因であることが判明しました。

なので、Pages Router のテストを新しい記法にマイグレーションすることにしました。

記法の違いの例は以下ですが、詳しい違いについては前述したパッケージのnpmを確認してください、

Pages Router の記法

import { testApiHandler } from "next-test-api-route-handler";
import { getHandler } from "@pages/api/v1/todos/[id]";
import { getTodo } from "@services/todos/getTodo";

jest.mock("@services/todos/getTodo");

describe("/api/v1/todos/:id", () => {
  test("指定したIDのTODOを取得できる", async () => {
    (getTodo as jest.Mock).mockResolvedValue({
      id: 1234,
      title: "todo title",
      isCompleted: false,
    });
    await testApiHandler({
      pagesHandler: getHandler,
      params: { id: "1234" },
      test: async ({ fetch }) => {
        const res = await fetch({ method: "GET" });
        const data = await res.json();
        expect(data).toEqual({
          id: 1234,
          title: "todo title",
          isCompleted: false,
        });
      },
    });
  });
});

App Router の記法

import { testApiHandler } from "next-test-api-route-handler";
- import { getHandler } from "@pages/api/v1/todos/[id]";
+ import { getHandler } from "@app/api/v2/todos/[id]/_handlers/getHandler";
import { getTodo } from "@services/todos/getTodo";

jest.mock("@services/todos/getTodo");

- describe("/api/v1/todos/:id", () => {
+ describe("/api/v2/todos/:id", () => {
  test("指定したIDのTODOを取得できる", async () => {
    (getTodo as jest.Mock).mockResolvedValue({
      id: 1234,
      title: "todo title",
      isCompleted: false,
    });
    await testApiHandler({
-      pagesHandler: getHandler,
+      appHandler: {
+        GET: getHandler,
+      },
      params: { id: "1234" },
      test: async ({ fetch }) => {
        const res = await fetch({ method: "GET" });
        const data = await res.json();
        expect(data).toEqual({
          id: 1234,
          title: "todo title",
          isCompleted: false,
        });
      },
    });
  });
});

3. エラー発生と対策

Pages Router を next-test-api-route-handlerv4.0.7 に併せた記法にマイグレーションが完了しました。

しかし、テストは以下のエラーで失敗しました。

clearImmediate is not defined
ReferenceError: clearImmediate is not defined

このエラーは、 core-jsconnect.js で使用されている clearImmediate が定義されていないことが原因のようでした。

そこで、 jest.polyfills.js に次の設定を追加しました。

jest.polyfills.js
const { clearImmediate } = require("node:timers");
Object.defineProperties(globalThis, {
  clearImmediate: { value: clearImmediate },
})

4. 次のエラーと最終対策

しかし、この対策を施しても次のエラーが発生しました。

Error: Uncaught [TypeError: markResourceTiming is not a function]

このエラーについては、以下のissueが参考になりました。

https://github.com/mswjs/msw/issues/1851

これに基づいて、 jest.polyfills.js に次の設定を追加しました。

jest.polyfills.js
+ const { performance } = require('node:perf_hooks');
+ const { ReadableStream, TransformStream } = require("node:stream/web");
Object.defineProperties(globalThis, {
  clearImmediate: { value: clearImmediate },
+ TransformStream: { value: TransformStream },
+ clearImmediate: { value: clearImmediate },
+ performance: { value: performance },
});

この設定を追加したことで、テストは正常に動作するようになりました。

最終的な jest の設定

私の場合は下記の設定で動作しましたが、他のプロジェクトで必ずしも動くとは限りませんのでご留意ください。

jest.config.js
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nextJest = require('next/jest')

const createJestConfig = nextJest({
iles in your test environment
  dir: './',
})

const customJestConfig = {
  setupFiles: ['./jest.polyfills.js'],
  moduleNameMapper: {
    "@/(.*)$": "<rootDir>/src/$1",
    "@services/(.*)$": "<rootDir>/src/services/$1",
    "@pages/(.*)$": "<rootDir>/src/pages/$1",
    "^@app/(.*)$": "<rootDir>/src/app/$1",
  },
  moduleDirectories: ['node_modules', '<rootDir>/'],
  testEnvironment: "jest-environment-jsdom",
  setupFilesAfterEnv: ['<rootDir>/src/lib/singleton.ts'],
  testEnvironmentOptions: {
    customExportConditions: [''],
  }
};

module.exports = async () => {
  return {
    ...(await createJestConfig(customJestConfig)()),
    transformIgnorePatterns: ["node_modules/(?!(jose|next-auth|@panva|uuid)/)"],
  }
};
jest.polyfills.js
const { performance } = require("node:perf_hooks");
const { TextDecoder, TextEncoder } = require("node:util");
const { ReadableStream, TransformStream } = require("node:stream/web");
const { clearImmediate } = require("node:timers");

Object.defineProperties(globalThis, {
  TextDecoder: { value: TextDecoder },
  TextEncoder: { value: TextEncoder },
  ReadableStream: { value: ReadableStream },
  TransformStream: { value: TransformStream },
  clearImmediate: { value: clearImmediate },
  performance: { value: performance },
});

const { Blob, File } = require("node:buffer");
const { fetch, Headers, FormData, Request, Response } = require("undici");

Object.defineProperties(globalThis, {
  fetch: { value: fetch, writable: true },
  Blob: { value: Blob },
  File: { value: File },
  Headers: { value: Headers },
  FormData: { value: FormData },
  Request: { value: Request },
  Response: { value: Response },
});

おわりに

今回の問題は、プロジェクトに多数のパッケージが既にインストールされていることが一因である可能性があります。

不要なライブラリを削減することで、同様のエラーは回避できるかもしれませんが、複数のパッケージが共存するアプリケーション開発の現場では、このようなトラブルシューティングは避けられない部分もあると感じました。

以上が、Next.js の API Routes から Route Handler への移行とテストの備忘録となります。

この記事が同様の状況に直面している方に、少しでも役立てば幸いです。

株式会社HAMWORKS
株式会社HAMWORKS

Discussion