next-test-api-route-handler のアップデートによるエラー解決の備忘録
はじめに
この記事は Next.js のAPIテストに利用した next-test-api-route-handler
を v3.2.0
から v4.0.7
にアップデートした際に発生したエラーの対処法の備忘録になります。
対象読者
- Next.js を使っている人
- Pages Router から App Router に移行する人
-
next-test-api-route-handler
を利用してテストを書いている人 -
next-test-api-route-handler
をv3
=>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-handler
の v4.0.7
に併せた記法にマイグレーションが完了しました。
しかし、テストは以下のエラーで失敗しました。
clearImmediate is not defined
ReferenceError: clearImmediate is not defined
このエラーは、 core-js
の connect.js
で使用されている clearImmediate
が定義されていないことが原因のようでした。
そこで、 jest.polyfills.js
に次の設定を追加しました。
const { clearImmediate } = require("node:timers");
Object.defineProperties(globalThis, {
clearImmediate: { value: clearImmediate },
})
4. 次のエラーと最終対策
しかし、この対策を施しても次のエラーが発生しました。
Error: Uncaught [TypeError: markResourceTiming is not a function]
このエラーについては、以下のissueが参考になりました。
これに基づいて、 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 の設定
私の場合は下記の設定で動作しましたが、他のプロジェクトで必ずしも動くとは限りませんのでご留意ください。
// 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)/)"],
}
};
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 への移行とテストの備忘録となります。
この記事が同様の状況に直面している方に、少しでも役立てば幸いです。
Discussion