2026-03-29 09:24:23
JavaScript单元测试与Mocking是为了确保代码在隔离环境下稳定、快速且可重复地验证行为,解决依赖外部系统导致的测试不可控问题。
单元测试的核心目标:验证最小代码单元(如函数、类方法)的行为是否符合预期,需排除外部系统干扰。若被测代码依赖网络请求、数据库或时间函数(如Date.now()),直接测试会导致:
速度变慢:等待外部响应延长测试时间。
结果不稳定:网络波动或服务不可用导致测试失败。
场景模拟困难:难以复现错误响应或特定数据状态。
Mocking的作用:通过伪造依赖项(如API、数据库)的输出和行为,使测试精准可控。例如:
替换fetch请求为固定返回值,避免真实网络调用。
模拟数据库错误,验证代码容错性。
固定时间函数结果,确保时间相关逻辑可预测。
Jest框架的核心工具:
jest.fn():创建Mock函数,记录调用次数、参数,并设定返回值。const mockFetch = jest.fn();mockFetch.mockReturnValue({ name: 'Alice' });
jest.mock():自动替换整个模块为Mock版本,适用于外部依赖(如Axios)。jest.mock('axios');axios.get.mockResolvedValue({ data: { id: 1, name: 'Bob' } });
异步场景支持:
mockResolvedValue:模拟异步成功(如Promise.resolve)。
mockRejectedValue:模拟异步失败(如Promise.reject)。
参数验证:toHaveBeenCalledWith检查函数是否按预期调用。expect(mockFetch).toHaveBeenCalledWith(123);
Mocking的实践场景:
基础返回值控制:固定依赖项的输出,测试被测函数逻辑。test('processUser calls fetchUser with correct ID', () => { const mockFetch = jest.fn().mockReturnValue({ name: 'Alice' }); const result = processUser(mockFetch, 123); expect(result).toBe('Processed: Alice');});
异步API测试:模拟异步请求的成功与失败。test('fetchUserData handles error', async () => { axios.get.mockRejectedValue(new Error('Network error')); await expect(fetchUserData(1)).rejects.toThrow('Network error');});
复杂逻辑模拟:根据参数动态返回不同结果。const mockFs = { readFile: jest.fn((path, cb) => { if (path === 'valid.json') cb(null, '{"name":"John"}'); else cb(new Error('File not found'), null); })};
避免副作用的关键操作:
清理Mock状态:使用clearAllMocks()或restoreAllMocks()防止跨测试污染。afterEach(() => { jest.clearAllMocks();});
全局对象Mock的恢复:修改原生对象(如Date、console)后需恢复原始实现。beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {});});afterEach(() => { jest.restoreAllMocks();});
Mocking的设计原则:
隔离性:仅测试目标代码,不依赖真实外部系统。
可控性:通过Mock精确控制依赖项的行为(如返回特定错误码)。
可重复性:确保测试在任何环境下结果一致。
总结:JavaScript单元测试与Mocking通过隔离依赖项,解决了外部系统导致的不稳定、慢速和难以模拟的问题。Jest提供的jest.fn()、jest.mock()等工具,结合异步支持和清理机制,能够高效实现精准测试。掌握Mocking的核心是理解“隔离”原则,合理使用工具可显著提升测试质量和开发效率。