App Router における Route Handler (route.ts) のテスト

忙しい人向け

example.test.ts

import { NextRequest, NextResponse } from 'next/server'
import { GET, POST } from './route'

it('works fine for GET', async () => {
  const request = new NextRequest('http://example.com/')

  const result: NextResponse = await GET(request)

  expect(result.status).toBe(200)
})

it('works fine for POST', async () => {
  const data = { hoge: "fuga" }
  const request = new NextRequest('http://example.com/', {
    method: 'POST',
    body: JSON.stringify(data),
  })
  
  const result: NextResponse = await POST(request)

  expect(result.status).toBe(200)
})

Route Handlerとテスト

Route Handler は Next.js の App Router から追加された概念で、Pages Router における API Routes に近い概念です。HTML を返す page.tsx 等とは違い、JSON 等を返します。

具体的に Route Handler は以下のような書き方をします。

route.ts

export async function GET(request: Request) {
  // 任意の処理
  return Response.json({ status: "OK" }) // 任意のオブジェクト
}

よってテストはこのメソッドについての挙動を確認すれば良いので、以下のようになります。

unit.test.ts

import { GET, POST } from './route'

it('works fine', async () => {
  const request: Request = // request を生成
  const result: Response = await GET(request)
  expect(result.status).toBe(200)
})

Requestの生成

Mock Request の生成には外部ライブラリ等を使用しないと複雑なのではと勝手に思っていましたが、シンプルに new で Request インスタンスを作成してやれば良いようです。

create-mock-request.ts

// GET
const getReq = new Request("https://example.com/")

// POST
const postReq = new Request("https://example.com/", {
  method: "POST",
  body: JSON.stringify({ hoge: "fuga" }),
})

NextRequestを利用する

Next.js の Route Handler において実際に第一引数として渡されるオブジェクトは NextRequest であり、Cookie や request.nextUrl.searchParams 等の追加データを扱うことを可能にします。NextRequest は Request クラスを継承しているため、Request 型で実装しているエンドポイントに対しても NextRequest を渡すことが可能です。

これといった理由が無い限り NextRequest でテストした方が良いです。コンストラクタの形についても互換性を保っているため、Request を NextRequest に置き換えるだけで移行できます。

create-next-request.ts

import { NextRequest } from 'next/server'

// GET
const getReq = new NextRequest("https://example.com/")

// POST
const postReq = new NextRequest("https://example.com/", {
  method: "POST",
  body: JSON.stringify({ hoge: "fuga" }),
})

クエリ文字列を利用する

生成する Request にクエリ文字列の情報を含めるには、シンプルに URL をクエリ文字列を含むものに変更します。

create-next-request.ts

const req = new NextRequest("https://example.com/?foo=bar")

// or

const searchParams = new URLSearchParams()
searchParams.set('foo', 'bar')

const req = new NextRequest(`https://example.com/?${searchParams.toString()}`)

後者の書き方でも最終的なURLは前者のURLになるのでどちらでも良いのですが、個人的には後者の方が読みやすいし編集しやすいので好きです。

最終的な成果物

example.test.ts

import { NextRequest, NextResponse } from 'next/server'
import { POST } from './route'

it('works fine for POST', async () => {
  const data = { hoge: "fuga" }

  const searchParams = new URLSearchParams()
  searchParams.set('foo', 'bar')

  const request = new NextRequest(`https://example.com/?${searchParams.toString()}`, {
    method: 'POST',
    body: JSON.stringify(data),
  })
  
  const result: NextResponse = await POST(request)

  expect(result.status).toBe(200)
})

余談

テストを定義しておくとリファクタリングの時の安心感が段違いでとても良い
逆にテストを修正するときは神経がすり減る