
在 jest 中,`jest.mock()` 无法在 `it()` 内部动态生效;正确做法是顶层调用 `jest.mock()` 启用自动模拟,再结合 `jest.spyon()` + `mockimplementation()` 在每个测试中覆盖具体行为,并通过 `aftereach` 清理状态,确保测试隔离。
要为每个 it() 测试用例提供完全独立、互不干扰的模块 mock 实现(例如不同行为的中间件),关键在于理解 Jest 模块模拟的生命周期和隔离机制。直接在 it() 内调用 jest.mock() 是无效的——Jest 仅在模块加载时(即文件顶部)处理 jest.mock() 调用,运行时调用会被忽略。
✅ 正确且推荐的三步策略如下:
1. 顶层 jest.mock() 启用自动模拟
在文件顶部(describe 外或 describe 内顶部)调用 jest.mock(),启用对目标模块的自动模拟(auto-mock)。这会生成一个带默认 jest.fn() 行为的模拟模块,但不定义具体实现:
// test.js —— 文件顶部
jest.mock('../../../middleware/awsTransferMiddleware');
const awsTransferMiddleware = require('../../../middleware/awsTransferMiddleware');⚠️ 注意:必须 require()(而非 import)该模块,否则 jest.spyOn() 将无法访问其属性(ESM 静态导入在 Jest 中不支持运行时重写)。
2. 使用 jest.spyOn() 动态覆盖方法实现
在每个 it() 中,使用 jest.spyOn(对象, 方法名) 获取对模拟函数的引用,并调用 .mockImplementation() 设置当前测试专属逻辑:
it("Should return 200 if at least one image exists", async () => {
// ✅ 覆盖 transferS3Files:模拟成功上传
jest.spyOn(awsTransferMiddleware, 'transferS3Files')
.mockImplementation(async (req, res, next) => {
req.s3TransferResult = { success: true, count: 2 };
next();
});
// ✅ 覆盖 filterPassedImage:模拟筛选出 1 张有效图
jest.spyOn(awsTransferMiddleware, 'filterPassedImage')
.mockImplementation(async (req, res, next) => {
req.filteredImages = ['img1.jpg'];
next();
});
const response = await request(app).post('/upload');
expect(response.status).toBe(200);
});it("Should return 400 when S3 upload fails", async () => {
// ❌ 完全不同的行为:模拟 transferS3Files 抛错
jest.spyOn(awsTransferMiddleware, 'transferS3Files')
.mockImplementation(async (req, res, next) => {
const err = new Error('S3 timeout');
err.status = 503;
next(err);
});
// ✅ filterPassedImage 不执行(因 transferS3Files 已调用 next(err))
jest.spyOn(awsTransferMiddleware, 'filterPassedImage')
.mockImplementation(() => {
throw new Error('This should not be called');
});
const response = await request(app).post('/upload');
expect(response.status).toBe(400); // 假设错误被全局 handler 转换为 400
});3. afterEach 全面清理,保障测试纯净性
在每个测试结束后,清除所有 mock 状态,防止“泄漏”到下一个用例:
afterEach(() => {
jest.restoreAllMocks(); // 恢复所有被 spy 的原始方法(如需)
jest.clearAllMocks(); // 清空所有 mock 函数的调用记录与返回值
});✅ jest.clearAllMocks() 是核心:它重置 mockImplementation、mockReturnValue、调用计数等,使下个 it() 可安全重新设置。
? 为什么不用 jest.resetModules()?
jest.resetModules() 会重新 require 模块,但对已 require 的模块实例(如 app 或 request 中依赖的中间件)无效,且可能破坏 supertest 的应用实例缓存。而 spyOn + clearAllMocks 更精准、轻量、可靠。
✅ 最终完整结构示例
// test.js
const request = require('supertest');
const app = require('../../../path/to/your/app');
// Step 1: Top-level mock
jest.mock('../../../middleware/awsTransferMiddleware');
const awsTransferMiddleware = require('../../../middleware/awsTransferMiddleware');
// Step 2: Cleanup after each test
afterEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
});
describe('POST /upload', () => {
it('returns 200 on successful transfer and filtering', async () => {
jest.spyOn(awsTransferMiddleware, 'transferS3Files')
.mockImplementation(async (req, res, next) => {
req.s3TransferResult = { success: true };
next();
});
jest.spyOn(awsTransferMiddleware, 'filterPassedImage')
.mockImplementation(async (req, res, next) => {
req.filteredImages = ['a.jpg'];
next();
});
const res = await request(app).post('/upload');
expect(res.status).toBe(200);
});
it('returns 500 when S3 transfer throws', async () => {
jest.spyOn(awsTransferMiddleware, 'transferS3Files')
.mockImplementation(async () => {
throw new Error('Network error');
});
const res = await request(app).post('/upload');
expect(res.status).toBe(500);
});
});? 总结:Jest 的模块 mock 隔离不靠“多次 jest.mock()”,而靠 “一次模拟 + 多次 spyOn().mockImplementation() + 每次 clearAllMocks()”。这是官方推荐、稳定高效、符合测试隔离原则的最佳实践。










