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

Next.js 13订阅API单元测试:node-mocks-http模拟请求报错问题

问题:Next.js 13 API路由单元测试报错TypeError: req.json is not a function

使用Next.js 13(版本13.4.2)和Prisma(版本5.3.1)实现邮件订阅API路由,编写单元测试时用node-mocks-http模拟请求,运行测试出现TypeError: req.json is not a function错误。官方文档未说明如何测试非handler形式的端点,相关代码及报错信息如下:

API路由代码(/.route.ts)

import prisma from '@/lib/client';
import { response200, response400, response500 } from '@/utils/responses';
import { validateEmail } from '@/utils/validators';

type BodyType = {
    email: string;
};

export async function POST(req: Request) {
    const body: BodyType = await req.json();

    if (!body.email) {
        return response400({ message: 'Email is required' });
    }

    if (!validateEmail(body.email)) {
        return response400({ message: 'Invalid email' });
    }

    try {
        const subscription = await prisma.newsletter.upsert({
            where: {
                email: body.email,
            },
            update: {
                active: true,
            },
            create: {
                email: body.email,
            },
        });

        if (process.env.DEV_ENV == 'production') {
            // TODO send email for activation
            console.log('production');
        } else {
            console.log(
                `http://localhost:3000/api/newsletter/activate/${subscription.id}`,
            );
        }

        return response200({ message: 'subscribed' });
    } catch (error) {
        return response500({
            message: 'Newsletter api is currently broken',
        });
    }
}

测试代码(/.test.ts)

import { prismaMock } from '@/lib/singleton';
import httpMocks from 'node-mocks-http';
import { POST } from './route';

describe('Newsletter subscription', () => {
    test('Subscribe test', async () => {
        const subscription = {
            id: '5e687d2b-b61f-47ec-959c-753bfdf0e15c',
            email: 'test@email.pl',
            confirmed: false,
            active: true,
            createdAt: new Date(Date.now()),
            updatedAt: new Date(Date.now()),
        };

        prismaMock.newsletter.upsert.mockResolvedValue(subscription);

        const { req, res } = httpMocks.createMocks({
            method: 'POST',
            body: {
                email: 'test@email.pl',
            },
        });

        const resp = await POST(req);
        expect(resp.status).toBe(200);
    });
});

报错信息

TypeError: req.json is not a function

       8 |
       9 | export async function POST(req: Request) {
    > 10 |     const body: BodyType = await req.json();                                   ^

      at json (app/api/newsletter/sing-up/route.ts:10:38)
      at Object.<anonymous> (app/api/newsletter/sing-up/sign-up.test.ts:27:32)

解决方案

问题核心是node-mocks-http生成的请求对象不符合Next.js 13 App Router API路由要求的Web标准Request接口,缺少json()方法。以下两种方法可解决:

方法1:给模拟请求手动添加json()方法

修改测试代码,在创建mock请求后为req对象补充json()方法:

const { req, res } = httpMocks.createMocks({
    method: 'POST',
    body: {
        email: 'test@email.pl',
    },
});

// 添加json方法,返回预设的请求体
req.json = async () => req.body;

const resp = await POST(req);
expect(resp.status).toBe(200);

方法2:直接使用Web标准Request对象模拟

无需依赖node-mocks-http,直接创建符合标准的Request实例:

// 替换原mock请求创建代码
const req = new Request('http://localhost:3000/api/newsletter/sign-up', {
    method: 'POST',
    body: JSON.stringify({ email: 'test@email.pl' }),
    headers: { 'Content-Type': 'application/json' },
});

const resp = await POST(req);
expect(resp.status).toBe(200);

补充说明

  • Next.js 13 App Router的API路由基于Web标准的Request/Response对象,而非传统Express风格的req/res。
  • 使用标准Request对象模拟更贴合实际运行环境,若需扩展headers、query等属性,直接在Request构造参数中配置即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 16:23:14