
jest 的 `jest.mock()` 默认是模块级静态替换,无法直接在 `it()` 内动态重定义;正确做法是:顶层 `jest.mock()` + `jest.spyon()` 动态覆盖 + `aftereach` 清理,从而实现每个测试用例独享定制化 mock 行为。
在 Jest 单元测试中,若需为不同 it() 测试用例提供完全独立、互不干扰的模块 mock 实现(例如模拟中间件 awsTransferMiddleware 的不同行为),直接在 it() 内多次调用 jest.mock() 是无效的——因为 jest.mock() 是 hoisted 且仅在模块初始化时生效,重复调用不会覆盖已注册的 mock。
✅ 正确且推荐的实践方案如下:
1. 顶层静态 mock(必需)
首先在文件顶部执行 jest.mock(),强制 Jest 替换目标模块为自动 mock(auto-mock)版本。这一步不可省略,否则 require() 将加载真实模块:
// 顶部:启用自动 mock(不提供实现,仅生成 jest.fn() 占位符)
jest.mock('../../../middleware/awsTransferMiddleware');2. 显式导入被 mock 模块并动态控制行为
由于自动 mock 会将模块导出全部转为 jest.fn(),我们需显式导入它,并在每个 it() 中通过 jest.spyOn() 或直接赋值来定制具体函数的行为:
const awsTransferMiddleware = require('../../../middleware/awsTransferMiddleware');
// 注意:此处 import/require 必须在 jest.mock() 之后,确保拿到的是 mock 版本3. 每个 it() 内独立配置 mock 实现
利用 jest.fn().mockImplementation() 为每个测试用例设置专属逻辑。推荐直接覆写导出对象的属性(更直观),而非 spyOn(因 auto-mock 已是 mock 函数):
it("Should return a 200 status code if at least one image exists in the temp directory.", async () => {
// ✅ 覆盖指定方法的实现(仅影响当前 test)
awsTransferMiddleware.transferS3Files.mockImplementation(async (req, res, next) => {
// 模拟成功上传场景
req.uploadedFiles = [{ key: 'img1.jpg', size: 1024 }];
next();
});
awsTransferMiddleware.filterPassedImage.mockImplementation(async (req, res, next) => {
req.filteredImages = req.uploadedFiles;
next();
});
const response = await request(app).post('/upload');
expect(response.status).toBe(200);
});
it("Should return 400 if no images pass filtering.", async () => {
// ✅ 完全不同的行为:模拟过滤后无有效图片
awsTransferMiddleware.transferS3Files.mockImplementation(async (req, res, next) => {
req.uploadedFiles = [{ key: 'doc.pdf', size: 2048 }];
next();
});
awsTransferMiddleware.filterPassedImage.mockImplementation(async (req, res, next) => {
req.filteredImages = [];
res.status(400).json({ error: 'No valid images' });
});
const response = await request(app).post('/upload');
expect(response.status).toBe(400);
});4. 全局清理保障隔离性(关键!)
在 afterEach 中调用 jest.clearAllMocks()(或 jest.restoreAllMocks()),确保每个测试用例从干净状态开始:
afterEach(() => {
jest.clearAllMocks(); // 重置所有 mock 的调用记录和返回值
});⚠️ 注意事项:不要在 it() 内使用 jest.mock():它不会生效,且可能引发 Cannot mock [...] before it is required 报错。避免 jest.unmock():在 auto-mock 后手动 unmock 再 mock 会破坏一致性,增加复杂度。ESM 用户注意:若项目使用 ES Modules,需改用 import() 动态导入 + vi.mock()(Vitest)或确保 Jest 配置支持 ESM mock;但 CommonJS(require)方案在此场景下更稳定可靠。性能提示:clearAllMocks() 开销极小,远低于重复 mock() 带来的不可预测性,是值得的健壮性投资。
通过以上四步,你就能在保持测试高度隔离的同时,为每个业务场景精准构造中间件行为,真正实现“一测一模”,大幅提升端到端 API 测试的覆盖率与可维护性。










