Next.jsのAPIのテストがRequest is not definedで失敗する問題と解決方法

少しつまづいたので備忘録残します。

環境 Next.js 14.1 , App Route

APIルートの単体テスト

次のようなAPIルートがあります。

import { NextResponse } from "next/server";

export async function GET(request: Request) {
  return NextResponse.json({
    message: `Hello`,
  });
}

GETに対するjestを使った単体テストは次のように書けます。

import { GET } from "./route";

describe("GET /api/hello", () => {
  it("returns a message", async () => {
    const url = new URL("http://localhost/api/hello");
    const response = await GET(new Request(url));
    expect(response.status).toBe(200);
    expect(response.headers.get("content-type")).toBe("application/json");
    const body = await response.json();
    expect(body).toEqual({ message: "Hello" });
  });
});

このテストは通るはずですが、今回 ReferenceError: Request is not definedというエラーが出てテストが実行できない問題に遭遇しました。

原因 テスト環境がjsdomだった

jestのtestEnvironmentオプションでは、テストが実行される環境を指定することができます。

デフォルトはjsdomで、ブラウザ環境をエミュレートします。Reactコンポーネントやその他のブラウザ依存のコードをテストする場合に設定します。

testEnvironment: "jsdom",

一方、nodeを指定するとNode.jsの環境でテストが実行されます。サーバーサイドのコードはこちらを指定する必要があります。

testEnvironment: "node",

RequestオブジェクトやResponseオブジェクトはサーバーサイドでのみ利用できるので、jsdom環境でのテストでは参照できません。

解決方法としては、jest.config.tsでtestEnvironmentをnodeに変更するか、ファイルの先頭で環境を指定します。

/**
 * @jest-environment node
 */

import { GET } from "./route";

describe("GET /api/hello", () => {
  it("returns a message", async () => {
    const url = new URL("http://localhost/api/hello");
    const response = await GET(new Request(url));
    expect(response.status).toBe(200);
    expect(response.headers.get("content-type")).toBe("application/json");
    const body = await response.json();
    expect(body).toEqual({ message: "Hello" });
  });
});