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
相关产品推荐
相关产品推荐

