如何测试NextJS Pages Router中服务端发起的HTTP请求?
解决方案:Next.js Pages Router 多步骤漏斗表单测试方案
针对你的场景,分两种测试工具给出具体实现方案,优先推荐用 Playwright 完成端到端全流程验证,Jest 适合拆分单元测试覆盖核心逻辑:
一、Playwright 端到端测试:解决请求捕获问题
你抓不到服务端请求的核心原因是:如果后端请求是通过 Next.js API 路由转发(而非前端直接调用后端),Playwright 只能捕获浏览器发起的请求(即前端到 /api/* 的请求),无法直接看到 API 路由到后端的请求。以下是两种解决思路:
1. 直接验证前端到内部 API 路由的请求负载
假设你的表单提交是先调用 Next.js 的 /api/submit 路由,再由该路由转发到后端。此时只需验证前端传给 /api/submit 的数据即可(API 路由的转发逻辑可单独用 Jest 单元测试覆盖):
// tests/e2e/funnel.spec.js import { test, expect } from '@playwright/test'; test('完整遍历漏斗并验证提交负载', async ({ page }) => { // 进入漏斗第一步 await page.goto('/funnel/step1'); // 填写第一步字段并跳转 await page.fill('#name-input', 'John Doe'); await page.click('#btn-next-step1'); // 填写第二步字段并跳转 await page.fill('#email-input', 'john@example.com'); await page.click('#btn-next-step2'); // 填写第三步字段,准备提交 await page.fill('#phone-input', '1234567890'); // 监听提交请求并触发提交 const submitRequest = page.waitForRequest(req => req.url().includes('/api/submit') && req.method() === 'POST'); await page.click('#btn-submit'); // 验证请求负载 const request = await submitRequest; expect(request.postDataJSON()).toEqual({ name: 'John Doe', email: 'john@example.com', phone: '1234567890' }); });
2. 用 Mock Service Worker (MSW) 捕获后端请求
如果必须验证后端收到的真实负载,可以用 MSW 模拟后端服务,让所有后端请求走 MSW,从而捕获请求数据:
步骤1:配置 MSW 后端 Mock
// msw/handlers.js import { rest } from 'msw'; export const handlers = [ rest.post('https://your-real-backend.com/submit', (req, res, ctx) => { return res(ctx.json({ success: true })); }) ];
步骤2:在 Playwright 中启动 MSW
// playwright.config.js import { defineConfig } from '@playwright/test'; import { setupServer } from 'msw/node'; import { handlers } from './msw/handlers'; const server = setupServer(...handlers); export default defineConfig({ use: { baseURL: 'http://localhost:3000', async contextInit() { server.listen({ onUnhandledRequest: 'bypass' }); }, async contextDestroy() { server.close(); } } });
步骤3:在测试中捕获 MSW 收到的请求
// tests/e2e/funnel.spec.js import { test, expect } from '@playwright/test'; import { server } from '../msw/server'; test('验证后端收到的提交负载', async ({ page }) => { // 重置请求记录 server.resetHandlers(); // 遍历漏斗步骤(同前略)... // 捕获后端请求 let capturedPayload; server.use( rest.post('https://your-real-backend.com/submit', (req, res, ctx) => { capturedPayload = req.body; return res(ctx.json({ success: true })); }) ); await page.click('#btn-submit'); expect(capturedPayload).toEqual({ name: 'John Doe', email: 'john@example.com', phone: '1234567890' }); });
二、Jest 单元测试:覆盖核心逻辑与流程
Jest 适合拆分测试组件交互、Redux Saga、API 调用逻辑,要模拟漏斗导航和表单填写,需做以下 Mock:
1. Mock Next.js 路由与 Redux
// 全局 Mock Next.js 路由 jest.mock('next/router', () => ({ useRouter: () => ({ push: jest.fn() }) })); // Mock Redux Store 与 Saga import configureStore from 'redux-mock-store'; import createSagaMiddleware from 'redux-saga'; import { runSaga } from 'redux-saga'; const mockStore = configureStore([]); const sagaMiddleware = createSagaMiddleware();
2. 测试漏斗流程与 API 调用
import { render, screen, fireEvent } from '@testing-library/react'; import { Provider } from 'react-redux'; import FunnelStep1 from '../pages/funnel/step1'; import FunnelStep2 from '../pages/funnel/step2'; import FunnelStep3 from '../pages/funnel/step3'; import { submitFunnel } from '../services/api'; import { funnelSaga } from '../redux/sagas'; // Mock API 服务 jest.mock('../services/api', () => ({ submitFunnel: jest.fn().mockResolvedValue({ data: { success: true } }) })); test('遍历漏斗并验证提交负载', async () => { const store = mockStore({ funnel: { step: 1, data: {} } }); // 测试第一步:填写并跳转 render(<Provider store={store}><FunnelStep1 /></Provider>); fireEvent.change(screen.getByLabelText('姓名'), { target: { value: 'John Doe' } }); fireEvent.click(screen.getByText('下一步')); expect(require('next/router').useRouter().push).toHaveBeenCalledWith('/funnel/step2'); // 测试第二步:填写并跳转 render(<Provider store={store}><FunnelStep2 /></Provider>); fireEvent.change(screen.getByLabelText('邮箱'), { target: { value: 'john@example.com' } }); fireEvent.click(screen.getByText('下一步')); expect(require('next/router').useRouter().push).toHaveBeenCalledWith('/funnel/step3'); // 测试第三步:提交并验证 API 调用 render(<Provider store={store}><FunnelStep3 /></Provider>); fireEvent.change(screen.getByLabelText('手机号'), { target: { value: '1234567890' } }); fireEvent.click(screen.getByText('提交')); // 运行 Saga 验证 API 调用参数 await runSaga( { dispatch: () => {}, getState: () => store.getState() }, funnelSaga ).toPromise(); expect(submitFunnel).toHaveBeenCalledWith({ name: 'John Doe', email: 'john@example.com', phone: '1234567890' }); });
内容的提问来源于stack exchange,提问作者vtomic85
相关产品推荐
相关产品推荐

