本文讲解如何在 Jest 单元测试中防止因 Array.prototype.shift() 等原地修改操作导致 mock 数据跨测试用例污染,核心方案是:每个测试独立创建不可共享的 mock 数据副本,并确保异步函数正确 await。
本文讲解如何在 jest 单元测试中防止因 `array.prototype.shift()` 等原地修改操作导致 mock 数据跨测试用例污染,核心方案是:每个测试独立创建不可共享的 mock 数据副本,并确保异步函数正确 await。
在 Jest 测试中,若多个 test case 复用同一份引用类型的 mock 数据(如数组),且测试逻辑中对其执行了原地变更操作(如 shift()、push()、splice()),就会引发测试间数据污染——前一个测试修改了数组状态,后一个测试读取到已被篡改的数据,从而导致断言失败或行为不可预测。
上述问题的根本原因有三点:
- mockData 被定义在 describe 块顶层,所有测试用例共享同一数组引用;
- Helpers.execute() 中调用 getData() 后未 await,但 getData 是 async 函数,实际返回的是 Promise,而 data.shift() 作用于未解析的 Promise 对象,逻辑错误且掩盖了真实问题;
- jest.mock('./operations') 放在 beforeAll 内动态 mock,不仅冗余,还可能干扰模块初始化顺序;正确做法是在文件顶部以 module scope 方式静态 mock,确保模块加载前即完成模拟。
✅ 正确实践如下:
第一步:确保异步调用正确 await
Helpers.js 中必须 await getData(),否则 data 是 Promise 实例,shift() 无意义且不报错(返回 undefined),极易引发隐蔽 bug:
// helpers.js
import { getData, track } from './operations';
export class Helpers {
static async execute(request) {
if (request) await track(); // 注意:track 也应 await
const data = await getData(); // ✅ 关键:必须 await
return data.shift(); // ✅ 此时 data 是真实数组
}
}
第二步:每个测试用例独立声明 mock 数据
禁止将 mockData 提升至 describe 作用域。应在每个 it 块内重新创建新数组,确保引用隔离:
// helpers.test.js
import { Helpers } from './helpers';
import { getData, track } from './operations';
jest.mock('./operations'); // ✅ 模块级 mock,位于文件顶部
describe('Helpers', () => {
afterEach(() => {
jest.clearAllMocks(); // 清理 mock 调用记录与返回值
});
it('Should return a id 1', async () => {
const mockData = [{ id: 1 }, { id: 2 }, { id: 3 }]; // ✅ 每次新建数组
getData.mockResolvedValue(mockData);
const response = await Helpers.execute();
expect(response.id).toBe(1);
expect(getData).toHaveBeenCalledTimes(1);
});
it('Should return a id 1 with true flag', async () => {
const mockData = [{ id: 1 }, { id: 2 }, { id: 3 }]; // ✅ 独立副本,不受上一 test 影响
getData.mockResolvedValue(mockData);
track.mockResolvedValue({});
const response = await Helpers.execute(true);
expect(response.id).toBe(1);
expect(track).toHaveBeenCalledTimes(1);
});
});
第三步:导出 operations 函数为模块方法
确保 operations.js 使用 export 语法,使 Jest 能正确 mock:
// operations.js
export async function getData() {
// 实际业务逻辑
}
export async function track() {
// 实际追踪逻辑
}
⚠️ 注意事项:
- jest.clearAllMocks() 在 afterEach 中调用已足够,beforeAll 中的 jest.clearAllMocks() 和 jest.mock() 属于冗余甚至有害操作;
- 若需复用复杂 mock 数据结构,可封装为工厂函数(如 createMockData()),但绝不可直接导出或复用数组字面量引用;
- 对于更复杂的可变状态(如 Class 实例、Map/Set),推荐使用 structuredClone() 或深拷贝库(如 lodash.cloneDeep)生成隔离副本。
遵循以上三原则,即可彻底杜绝 mock 数据跨测试污染问题,保障单元测试的隔离性(Isolation) 与可靠性(Reliability)。











