Jest中模拟注入类、单测Mock隔离及Joi Mock冲突问题
问题解决方案
1. 模拟PaymentMessageRepository的methodOne抛出错误
要测试当PaymentMessageRepository的methodOne()抛错时PaymentProcessorService的响应,可通过局部Mock类方法实现,无需全局替换整个类的Mock:
test('当PaymentMessageRepository的methodOne抛错时,PaymentProcessorService应正确处理', () => { // 创建依赖实例 const repo = new PaymentMessageRepository(); const gorepo = new GoMessageRepository(); const sqsm = new PaymentMessageQueueManager(process.env.SOME_ENV, process.env.ANOTHER_ENV); // 模拟methodOne方法抛出错误 jest.spyOn(repo, 'methodOne').mockImplementation(() => { throw new Error('模拟methodOne抛出的错误'); }); // 初始化被测服务 const systemUnderTest = new PaymentProcessorService(repo, gorepo, sqsm); // 执行测试逻辑并验证结果 expect(() => systemUnderTest.someMethodThatCallsMethodOne()).toThrow('模拟methodOne抛出的错误'); // 也可验证其他预期行为,比如错误处理逻辑的调用情况 });
2. 修复Joi全局Mock导致的测试失效问题
你用jest.mock('joi')全局Mock了Joi,导致.assert()方法被覆盖,第一个测试用例(依赖缺失时应抛错)无法触发真实验证逻辑。有两种解决思路:
方案一:只为需要Mock Joi的测试用例启用Mock
移除顶部全局的jest.mock('joi'),在需要Mock的测试用例中使用jest.doMock,测试后恢复:
// 移除全局的jest.mock('joi') test('需要Mock Joi的测试用例', () => { jest.doMock('joi', () => ({ assert: jest.fn(), // 按需添加其他需要Mock的Joi方法 })); // 测试逻辑... // 测试结束后恢复Joi jest.unmock('joi'); }); // 第一个测试用例无需Mock Joi,直接使用真实实现 test('When a dependency is null, processor service should not be created', () => { process.env.PAYMENT_MESSAGE_TABLE_NAME = 'MOCK_TABLE_NAME'; const repo = new PaymentMessageRepository(); const gorepo = new GoMessageRepository(); const sqsm = new PaymentMessageQueueManager(process.env.SOME_ENV, process.env.ANOTHER_ENV); expect(() => new PaymentProcessorService(repo, undefined, sqsm)).toThrow(Error); });
方案二:在特定测试用例中恢复Joi的真实实现
如果必须全局Mock Joi,可在第一个测试用例中临时恢复真实的Joi:
jest.mock('joi'); // 全局Mock test('When a dependency is null, processor service should not be created', () => { // 导入真实的Joi const realJoi = jest.requireActual('joi'); // 替换Mock的assert方法为真实实现 jest.mocked(joi).assert.mockImplementation(realJoi.assert); process.env.PAYMENT_MESSAGE_TABLE_NAME = 'MOCK_TABLE_NAME'; const repo = new PaymentMessageRepository(); const gorepo = new GoMessageRepository(); const sqsm = new PaymentMessageQueueManager(process.env.SOME_ENV, process.env.ANOTHER_ENV); expect(() => new PaymentProcessorService(repo, undefined, sqsm)).toThrow(Error); // 测试结束后重置Mock jest.mocked(joi).assert.mockReset(); });
3. 为单个测试用例配置Mock并隔离
要实现测试用例间的Mock隔离,避免全局Mock影响所有测试,可采用以下方法:
方法一:使用jest.spyOn局部Mock类方法
针对单个测试用例仅Mock需要的方法,测试后可自动重置:
test('当GoMessageRepository的some_method返回0时,验证处理器行为', () => { const gorepo = new GoMessageRepository(); // Mock some_method返回0 jest.spyOn(gorepo, 'some_method').mockReturnValue(0); // 创建其他依赖实例 const repo = new PaymentMessageRepository(); const sqsm = new PaymentMessageQueueManager(process.env.SOME_ENV, process.env.ANOTHER_ENV); const systemUnderTest = new PaymentProcessorService(repo, gorepo, sqsm); // 执行测试并验证结果 const result = systemUnderTest.someMethodThatUsesSomeMethod(); expect(result).toBe(/* 你的预期值 */); // 可选:恢复方法的原始实现 gorepo.some_method.mockRestore(); }); test('当GoMessageRepository的some_method抛错时,验证处理器行为', () => { const gorepo = new GoMessageRepository(); jest.spyOn(gorepo, 'some_method').mockImplementation(() => { throw new Error('模拟错误'); }); // 测试逻辑... expect(() => systemUnderTest.someMethodThatUsesSomeMethod()).toThrow('模拟错误'); });
方法二:在beforeEach/afterEach中重置Mock
如果使用全局Mock,可在每个测试前后重置Mock状态,避免测试间的影响:
describe('PaymentProcessorService unit tests', () => { const env = process.env; beforeEach(() => { jest.resetModules(); process.env = { ...env }; // 重置所有类的Mock状态 jest.clearAllMocks(); }); afterEach(() => { process.env = env; }); test('测试用例1', () => { // 自定义当前测试的Mock行为 jest.mocked(PaymentMessageRepository).mockImplementation(() => ({ methodOne: jest.fn().mockThrow(new Error('测试错误')), putItem: jest.fn() })); // 测试逻辑... }); test('测试用例2', () => { // 自定义另一种Mock行为 jest.mocked(PaymentMessageRepository).mockImplementation(() => ({ methodOne: jest.fn().mockReturnValue('正常返回'), putItem: jest.fn() })); // 测试逻辑... }); });
方法三:使用jest.doMock为单个测试用例替换Mock
jest.doMock仅影响当前测试用例,不会全局生效:
test('单个测试用例的自定义Mock', () => { // 替换PaymentMessageRepository的Mock实现 jest.doMock('../Repository/PaymentMessageRepository', () => ({ PaymentMessageRepository: jest.fn().mockImplementation(() => ({ methodOne: jest.fn().mockThrow(new Error('模拟错误')), putItem: jest.fn() })), PaymentMessageRepository_Schema: { required: jest.fn() } })); // 必须重新导入类,因为jest.doMock需要重新加载模块 const { PaymentMessageRepository } = require('../Repository/PaymentMessageRepository'); const { PaymentProcessorService } = require('../Services/PaymentProcessorService'); const repo = new PaymentMessageRepository(); const gorepo = new GoMessageRepository(); const sqsm = new PaymentMessageQueueManager(process.env.SOME_ENV, process.env.ANOTHER_ENV); const systemUnderTest = new PaymentProcessorService(repo, gorepo, sqsm); // 测试逻辑... });
内容的提问来源于stack exchange,提问作者David Merinos
相关产品推荐
相关产品推荐

