Swift中如何对带泛型依赖的ViewModel方法进行单元测试
核心结论
你不需要为了单元测试完全放弃泛型设计,也不必须强制使用存在类型改造代码。当前的测试阻碍本质不是泛型本身,而是ViewModel里硬编码依赖导致的,两种改造方案都可以满足测试需求,根据业务实际场景选择即可。
方案1:保留原有泛型设计,零侵入完成单测
如果你的calculate方法确实需要泛型能力(比如需要静态类型检查、要关联其他泛型参数、基于具体类型做逻辑分发),完全不需要修改协议的泛型定义,只需要做两个小调整即可:
- 给ViewModel增加依赖注入入口,不要在类内部硬编码
useCase和item的初始化 - 编写遵循
UseCaseProtocol的Mock类,用来记录方法调用行为
改造后的业务代码:
class A_ViewModel { var useCase: UseCaseProtocol var item: A_CellModel // 构造函数注入依赖,默认参数保持原有业务调用逻辑不变 init(useCase: UseCaseProtocol = UseCase(), item: A_CellModel = .init()) { self.useCase = useCase self.item = item } func prepare() { // 原有业务逻辑完全不需要改动 useCase.calculate(item: item) } } // 原有协议、UseCase、Model定义完全保留,不需要修改 protocol TestableProtocol { var testProperty: Bool { get set } } class A_CellModel: TestableProtocol { var testProperty: Bool = false } protocol UseCaseProtocol { func calculate<T: TestableProtocol>(item: T) } class UseCase: UseCaseProtocol { func calculate<T: TestableProtocol>(item: T) { // 原有业务逻辑不变 } }
测试用的Mock实现和单测代码:
class MockUseCase: UseCaseProtocol { // 记录方法调用状态和传入参数 var isCalculateCalled = false var receivedItem: (any TestableProtocol)? func calculate<T: TestableProtocol>(item: T) { isCalculateCalled = true receivedItem = item } } func test_prepare_willCallCalculateWithCorrectItem() { // 初始化测试依赖 let mockUseCase = MockUseCase() let testItem = A_CellModel() testItem.testProperty = true let sut = A_ViewModel(useCase: mockUseCase, item: testItem) // 执行被测方法 sut.prepare() // 验证行为符合预期 XCTAssertTrue(mockUseCase.isCalculateCalled) let passedItem = mockUseCase.receivedItem as? A_CellModel XCTAssertEqual(passedItem?.testProperty, true) }
这个方案改动量最小,完全保留泛型的类型安全优势,是优先推荐的选择。
方案2:泛型无实际收益时,用存在类型简化代码
如果你的calculate方法内部逻辑完全不依赖具体类型,所有操作都是基于TestableProtocol定义的属性/方法完成,泛型没有带来实际的额外收益,可以直接用存在类型(any TestableProtocol)替换泛型定义,简化代码和Mock逻辑:
// 改造协议定义 protocol UseCaseProtocol { func calculate(item: any TestableProtocol) } class UseCase: UseCaseProtocol { func calculate(item: any TestableProtocol) { // 原有逻辑如果只操作testProperty,完全不需要修改 } }
改造后Mock不需要写泛型声明,写法更简单,ViewModel后续如果需要切换其他遵循TestableProtocol的Model类型,也不需要修改调用逻辑。注意如果你的方法需要用到具体类型的特性、或者需要关联其他泛型约束,不要用这个方案,会丢失泛型的静态类型检查能力。
避坑提醒
- 不要为了测试强行把泛型改成具体的
A_CellModel类型,会破坏代码的通用性 - 不要在业务代码里插入测试专用的判断逻辑,会污染生产代码
- 单测解耦的核心是依赖注入,和是否使用泛型没有直接关系
内容的提问来源于stack exchange,提问作者Dev.MJ
相关产品推荐
相关产品推荐

