You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Next.js 12的Pages中间件编写测试?

Next.js 12 Pages中间件测试完整示例

针对你提供的中间件,下面是一套完整的测试方案,覆盖所有业务逻辑:

1. 安装测试依赖

首先安装必要的测试工具(假设你的项目是TypeScript):

npm install --save-dev jest @types/jest ts-jest @types/node

如果是JavaScript项目,去掉@types/jest和ts-jest,改用babel-jest适配。

2. 创建测试文件

在项目根目录创建middleware.test.ts(对应你的middleware.ts文件位置),内容如下:

import { middleware } from './middleware';
import { NextRequest, NextResponse } from 'next/server';
import * as jose from 'jose';
import { USER_ROLES } from './path-to-your-roles-file'; // 替换成你的USER_ROLES实际路径

// Mock外部依赖模块
jest.mock('jose', () => ({
  decodeJwt: jest.fn(),
}));

// 模拟NextResponse的核心方法
jest.mock('next/server', () => ({
  ...jest.requireActual('next/server'),
  NextResponse: {
    redirect: jest.fn(() => ({})),
    next: jest.fn(() => ({})),
  },
}));

describe('Middleware Tests', () => {
  beforeEach(() => {
    // 每次测试前重置mock状态
    jest.clearAllMocks();
  });

  // 测试/admin路径的权限控制逻辑
  describe('/admin path authorization', () => {
    test('无auth token时重定向到首页', () => {
      const mockRequest = {
        nextUrl: { pathname: '/admin', searchParams: new URLSearchParams() },
        cookies: { token: undefined },
        url: 'http://localhost:3000/admin',
      } as unknown as NextRequest;

      middleware(mockRequest);

      expect(NextResponse.redirect).toHaveBeenCalledWith(
        new URL('/', mockRequest.url)
      );
    });

    test('有token但角色非admin时重定向到首页', () => {
      const mockToken = 'user-token';
      (jose.decodeJwt as jest.Mock).mockReturnValue({ role: USER_ROLES.user });

      const mockRequest = {
        nextUrl: { pathname: '/admin', searchParams: new URLSearchParams() },
        cookies: { token: mockToken },
        url: 'http://localhost:3000/admin',
      } as unknown as NextRequest;

      middleware(mockRequest);

      expect(NextResponse.redirect).toHaveBeenCalledWith(
        new URL('/', mockRequest.url)
      );
    });

    test('有admin角色token时允许正常访问', () => {
      const mockToken = 'admin-token';
      (jose.decodeJwt as jest.Mock).mockReturnValue({ role: USER_ROLES.admin });

      const mockRequest = {
        nextUrl: { pathname: '/admin', searchParams: new URLSearchParams() },
        cookies: { token: mockToken },
        url: 'http://localhost:3000/admin',
      } as unknown as NextRequest;

      middleware(mockRequest);

      expect(NextResponse.redirect).not.toHaveBeenCalled();
      expect(NextResponse.next).toHaveBeenCalled();
    });

    test('token无效(解码返回非对象)时重定向到首页', () => {
      const mockToken = 'invalid-token';
      (jose.decodeJwt as jest.Mock).mockReturnValue('invalid-payload');

      const mockRequest = {
        nextUrl: { pathname: '/admin', searchParams: new URLSearchParams() },
        cookies: { token: mockToken },
        url: 'http://localhost:3000/admin',
      } as unknown as NextRequest;

      middleware(mockRequest);

      expect(NextResponse.redirect).toHaveBeenCalledWith(
        new URL('/', mockRequest.url)
      );
    });
  });

  // 测试根路径的directToStore参数重定向逻辑
  describe('/ path with directToStore param', () => {
    test('带directToStore参数时重定向到/store', () => {
      const mockRequest = {
        nextUrl: {
          pathname: '/',
          searchParams: new URLSearchParams('directToStore=true'),
          url: 'http://localhost:3000/',
        },
        cookies: {},
      } as unknown as NextRequest;

      middleware(mockRequest);

      expect(NextResponse.redirect).toHaveBeenCalledWith(
        new URL('/store', mockRequest.url)
      );
    });

    test('不带directToStore参数时正常放行', () => {
      const mockRequest = {
        nextUrl: {
          pathname: '/',
          searchParams: new URLSearchParams(),
          url: 'http://localhost:3000/',
        },
        cookies: {},
      } as unknown as NextRequest;

      middleware(mockRequest);

      expect(NextResponse.redirect).not.toHaveBeenCalled();
      expect(NextResponse.next).toHaveBeenCalled();
    });

    test('非根路径带directToStore参数不触发重定向', () => {
      const mockRequest = {
        nextUrl: {
          pathname: '/other-page',
          searchParams: new URLSearchParams('directToStore=true'),
          url: 'http://localhost:3000/other-page',
        },
        cookies: {},
      } as unknown as NextRequest;

      middleware(mockRequest);

      expect(NextResponse.redirect).not.toHaveBeenCalled();
      expect(NextResponse.next).toHaveBeenCalled();
    });
  });
});

3. 配置Jest(TypeScript项目)

在项目根目录创建jest.config.js:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
  testMatch: ['**/*.test.ts', '**/*.test.js'],
};

4. 运行测试

在package.json的scripts中添加测试命令:

{
  "scripts": {
    "test": "jest"
  }
}

然后执行测试:

npm run test

关键说明

  • Mock外部依赖:通过jest.mock隔离jose.decodeJwt和NextResponse,避免测试依赖真实JWT解码或HTTP环境
  • 覆盖边界场景:除正常业务逻辑外,还测试了无效token、参数在非根路径等边缘情况
  • 精准断言:针对不同场景,明确断言是否触发重定向或正常放行

内容的提问来源于stack exchange,提问作者zeckdude

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 07:06:34