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

TypeScript+Jest中如何不设构造参数为可选来Mock依赖类?

在TypeScript+Jest环境下Mock依赖测试RouteController(对标Java Mockito实践)

需求背景

你在使用TypeScript+Jest测试RouteController时,希望像Java中Mockito那样Mock它的依赖RouteService,避免手动创建RouteService实例时需要解析其下游依赖(如UserSession)。RouteController通过tsyringe做依赖注入,构造函数声明必填的RouteService依赖。

可行方案及实现

方案一:手动注入Mock实例(与Java Mockito逻辑最接近)

直接构造RouteController时传入Jest Mock的RouteService实例,完全绕开DI容器,专注测试Controller的业务逻辑。

实现步骤:

  • 使用jest.mock()Mock整个RouteService模块,自动替换模块导出为Mock函数
  • 创建Mock实例并定义其方法的行为
  • 手动将Mock实例传入RouteController的构造函数
  • 执行测试方法并断言结果

测试代码示例:

import { RouteController } from './RouteController';
import { RouteService } from './RouteService';
import { RouteDTO } from './view/RouteDTO';

// Mock RouteService模块,自动替换所有导出为Jest Mock
jest.mock('./RouteService');

describe('RouteController', () => {
  let controller: RouteController;
  // 类型断言让TypeScript识别Mock对象的方法(如mockReturnValue)
  let mockRouteService: jest.Mocked<RouteService>;

  beforeEach(() => {
    // 重置Mock的调用记录和返回值,避免测试间污染
    jest.clearAllMocks();
    // 创建Mock的RouteService实例
    mockRouteService = new RouteService() as jest.Mocked<RouteService>;
    // 手动注入Mock到Controller
    controller = new RouteController(mockRouteService);
  });

  test('createRoute 业务逻辑有效时,调用saveRoute并返回结果', () => {
    // 准备测试数据
    const testRoute: RouteDTO = { name: 'test-route', path: '/test' };
    const mockSavedRoute: RouteDTO = { ...testRoute, id: '123' };

    // 定义Mock方法的返回值
    mockRouteService.saveRoute.mockReturnValue(mockSavedRoute);

    // 假设当前测试场景下业务逻辑有效(需根据实际代码调整isBusinessLogicValid的触发条件)
    const result = controller.createRoute(testRoute);

    // 断言结果匹配Mock返回值
    expect(result).toEqual(mockSavedRoute);
    // 断言saveRoute被正确调用一次,参数匹配
    expect(mockRouteService.saveRoute).toHaveBeenCalledTimes(1);
    expect(mockRouteService.saveRoute).toHaveBeenCalledWith(testRoute);
  });

  test('createRoute 业务逻辑无效时,抛出错误', () => {
    // 构造不符合业务逻辑的测试数据
    const invalidRoute: RouteDTO = { name: '', path: '/invalid' };

    // 执行方法并断言抛出指定错误
    expect(() => controller.createRoute(invalidRoute)).toThrow('Invalid business logic');
    // 断言saveRoute未被调用
    expect(mockRouteService.saveRoute).not.toHaveBeenCalled();
  });
});

方案二:结合tsyringe容器替换Mock依赖

如果你习惯通过DI容器获取RouteController实例,可以通过tsyringe的容器API替换RouteService为Mock实例,保持与生产环境一致的实例获取方式。

实现步骤:

  • MockRouteService模块
  • 创建Mock实例并注册到tsyringe容器,替换原有的RouteService绑定
  • 从容器中解析RouteController实例
  • 执行测试并断言

测试代码示例:

import { container } from 'tsyringe';
import { RouteController } from './RouteController';
import { RouteService } from './RouteService';
import { RouteDTO } from './view/RouteDTO';

jest.mock('./RouteService');

describe('RouteController (with tsyringe container)', () => {
  let controller: RouteController;
  let mockRouteService: jest.Mocked<RouteService>;

  beforeEach(() => {
    jest.clearAllMocks();
    mockRouteService = new RouteService() as jest.Mocked<RouteService>;
    // 注册Mock实例到容器,覆盖原有的RouteService绑定
    container.registerInstance(RouteService, mockRouteService);
    // 从容器中解析Controller实例
    controller = container.resolve(RouteController);
  });

  afterEach(() => {
    // 清空容器实例,避免测试间污染
    container.clearInstances();
  });

  test('createRoute 调用Mock的saveRoute并返回结果', () => {
    const testRoute: RouteDTO = { name: 'container-test', path: '/container' };
    const mockResult: RouteDTO = { ...testRoute, id: '456' };
    
    mockRouteService.saveRoute.mockReturnValue(mockResult);
    const result = controller.createRoute(testRoute);

    expect(result).toEqual(mockResult);
    expect(mockRouteService.saveRoute).toHaveBeenCalledWith(testRoute);
  });
});

关键注意事项

  • 类型支持:使用jest.Mocked<RouteService>类型断言,让TypeScript识别Mock对象的Jest专属方法(如mockReturnValue、toHaveBeenCalled),避免类型报错。
  • 业务逻辑可测试性:原代码中的isBusinessLogicValid如果是硬编码值,需调整为基于输入参数的判断逻辑,否则无法覆盖两个分支的测试。
  • 测试隔离:每次测试前调用jest.clearAllMocks()重置Mock状态,结合container.clearInstances()(方案二)确保测试间无状态污染。

内容的提问来源于stack exchange,提问作者João Pedro Schmitt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 06:20:43