测试 Next.js 应用路由器的 API 路由 - Arcjet 博客

作者:API传播员 · 2025-11-22 · 阅读时间:4分钟

关于软件安全的一大乐趣在于探索那些令人惊叹且复杂的攻击方法。例如,谷歌的 Project Zero 曾对一个在 iOS PDF 渲染组件中的假 GIF 文件中实现的图灵完备 CPU 进行分析,这个漏洞被用于开发 iMessage 的零点击攻击。这种复杂性让人叹为观止!

尽管这些漏洞很有趣,但普通用户通常不会受到这些复杂攻击的影响。实际上,大多数攻击往往是利用一些简单的安全漏洞,比如默认密码、过时软件中的已知问题或代码中的逻辑错误。这些问题在开发阶段可能难以发现,但在渗透测试中却经常暴露。然而,通过编写测试来验证代码的预期行为,可以有效避免这些问题随着代码的演变而产生回归。


测试 Next.js API 路由处理程序的挑战

在 Next.js 中测试 API 路由处理程序并非易事。官方文档主要关注前端组件的测试,而深入到 API 路由的测试时,你会发现并没有现成的解决方案。

Next.js 已经采用了 TypeScript 5.2+ 中的标准请求和响应类型,但同时也扩展了 NextRequestNextResponse 类型。此外,Next.js 对全局 fetch 函数进行了修补,增加了自定义 API 来访问头部信息或 Cookie,支持可选的段配置,并提供不同的运行时环境。

为了测试这些路由,你需要模拟自己的库,并启动一个服务器来发出请求。有些开发者尝试使用 node-mocks-http 来实现这一点,但并非总是成功。如果需要测试 Edge 运行时,问题会变得更加复杂。


如何测试 Next.js API 路由处理程序

幸运的是,next-test-api-route-handler 包解决了大部分问题。它与测试框架无关,可以模拟 Next.js 的应用路由器、页面路由器以及 Edge 运行时。

以下是一个基本的配置示例:

// jest.config.ts
import type { Config } from 'jest';
import nextJest from 'next/jest';

const createJestConfig = nextJest({
  dir: './', // 指定 Next.js 应用的路径
});

const config: Config = {
  coverageProvider: 'v8',
  testEnvironment: 'node',
  moduleNameMapper: {
    '^@/(.*)$': '/$1',
  },
  setupFilesAfterEnv: ['./jest.setup.ts'],
};

export default createJestConfig(config);
// jest.setup.ts
import '@testing-library/jest-dom';
// app/api/hello/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  return NextResponse.json({ hello: true }, { status: 200 });
}
// app/api/hello/route.test.ts
import { testApiHandler } from 'next-test-api-route-handler';
import * as appHandler from './route';

it('GET 返回 200', async () => {
  await testApiHandler({
    handler: appHandler,
    test: async ({ fetch }) => {
      const response = await fetch({ method: 'GET' });
      const json = await response.json();
      expect(response.status).toBe(200);
      expect(json).toStrictEqual({ hello: true });
    },
  });
});

运行 jest 命令即可执行测试。


模拟身份验证并测试经过身份验证的路由

以下是一个带有身份验证的 API 路由示例:

// app/api/hello/route.ts
import { authOptions } from '@/lib/auth';
import { getServerSession } from 'next-auth/next';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const session = await getServerSession(authOptions);
  if (!session || !session.user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  return NextResponse.json({ hello: true }, { status: 200 });
}

为了测试该路由,我们需要模拟 getServerSession 的调用:

// app/api/hello/route.test.ts
import { testApiHandler } from 'next-test-api-route-handler';
import type { Session } from 'next-auth';
import * as appHandler from './route';

let mockedSession: Session | null = null;

jest.mock('@/lib/auth', () => ({
  authOptions: { adapter: {}, providers: [], callbacks: {} },
}));

jest.mock('next-auth/next', () => ({
  getServerSession: jest.fn(() => Promise.resolve(mockedSession)),
}));

afterEach(() => {
  mockedSession = null;
});

it('未经身份验证时,GET 返回 401', async () => {
  mockedSession = null;
  await testApiHandler({
    handler: appHandler,
    test: async ({ fetch }) => {
      const response = await fetch({ method: 'GET' });
      const json = await response.json();
      expect(response.status).toBe(401);
      expect(json).toStrictEqual({ error: 'Unauthorized' });
    },
  });
});

it('通过身份验证时,GET 返回 200', async () => {
  mockedSession = { expires: 'expires', user: { id: 'test' } };
  await testApiHandler({
    handler: appHandler,
    test: async ({ fetch }) => {
      const response = await fetch({ method: 'GET' });
      const json = await response.json();
      expect(response.status).toBe(200);
      expect(json).toStrictEqual({ hello: true });
    },
  });
});

结论

通过以上方法,你可以轻松测试 Next.js 的 API 路由,包括模拟数据库调用、拆分模拟文件以及测试授权逻辑(例如用户是否有权限访问特定数据)。这些基础设置可以帮助你确保敏感的 API 路由在代码更改后依然安全可靠。

原文链接: https://blog.arcjet.com/testing-next-js-app-router-api-routes/