You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 16:53:20