Next.js + Jest にて middleware のテストをする

内容がけっこう薄いけど、
下記を参考にテストを書いてみる。

環境

  • Next.js v15.3
  • React v18
  • TypeScript v5
  • Jest v29
  • Node v22
  • App Router

サンプルコード

src/middleware.ts

import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

function findClientIPAddress(str: string | null): string {
	let res = ""
	if (typeof str == "string") {
		let arr: string[] = str.split(", ")
		if (arr.length > 0) {
			res = arr[0]
			arr = res.split(":")
			if (arr.length > 0) {
				res = arr[arr.length - 1]
			}
		}
	}
	return res
}

// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
	const response = NextResponse.next()

	let ipAddress = "unknown"

	if (request.headers.get("X-Forwarded-For")) {
		ipAddress = findClientIPAddress(request.headers.get("X-Forwarded-For"))
	}

	response.cookies.set({
		name: "ipAddress",
		value: ipAddress,
		path: "/",
		maxAge: 60 * 60 * 24,
	})

	return response
}

// See "Matching Paths" below to learn more
export const config = {
	matcher: "/((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
}

// テストしたい関数をここで export する
export const exportedForTesting = {
	findClientIPAddress,
}

src/middleware.test.ts

import { unstable_doesMiddlewareMatch } from "next/experimental/testing/server"
import { describe, expect, test } from "@jest/globals"
// import type { NextRequest } from "next/server"
import { NextRequest } from "next/server"
import { middleware, config, exportedForTesting } from "./middleware"
import nextConfig from "./../next.config"

describe("Middleware.ts", () => {
	test("matcher のテスト", () => {
		expect(
			unstable_doesMiddlewareMatch({
				config,
				nextConfig,
				url: "/",
			}),
		).toEqual(true)

		expect(
			unstable_doesMiddlewareMatch({
				config,
				nextConfig,
				url: "/favicon.ico",
			}),
		).toEqual(false)

		expect(
			unstable_doesMiddlewareMatch({
				config,
				nextConfig,
				url: "/test",
			}),
		).toEqual(true)
	})

	test("findClientIPAddress()", () => {
		const { findClientIPAddress } = exportedForTesting
		expect(findClientIPAddress("1::192.168.1.1, 2::192.168.1.2, 3::192.168.1.3")).toEqual("192.168.1.1")
	})

	test("Cookie に IP アドレスが入っているかどうか", async () => {
		const request = new NextRequest("http://localhost:8080", {
			headers: {
				"X-Forwarded-For": "1::192.168.1.1, 2::192.168.1.2, 3::192.168.1.3",
			},
		})
		const response = await middleware(request)
		expect(response.cookies.get("ipAddress").value).toEqual("192.168.1.1")
	})
})

docker-compose.yaml

services:
    nextjs-sample-app:
        image: node:22.9.0
        volumes:
            - ./app:/var/www/html
        ports:
            - 8080:3000
        tty: true
        working_dir: /var/www/html
        environment:
            # https://nextjs.org/docs/messages/non-standard-node-env
            #
            - NODE_ENV=development
        command: "npm run dev"