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

如何测试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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 00:05:13