Swift单元测试:如何Mock注入UIView的ApiProtocol方法?
最优实现方案:依赖注入 + 测试专属Mock类
你的问题核心在于MyUIView直接绑定了ApiProtocol的默认实现,导致测试时没法灵活替换stubMePlease()的返回值。最优雅的解法是通过依赖注入把ApiProtocol从MyUIView内部抽离出来,然后在每个测试用例里用自定义Mock来返回指定值,既解耦又灵活。
第一步:重构MyUIView,引入依赖注入
我们把ApiProtocol做成MyUIView的初始化参数,生产环境用默认实现,测试时可以替换成Mock:
protocol ApiProtocol { func stubMePlease() -> String } extension ApiProtocol { func stubMePlease() -> String { return "hello world" } } class MyUIView: UIView { private let api: ApiProtocol // 新增带api参数的初始化方法,默认用默认实现,不影响生产代码调用 init(frame: CGRect, api: ApiProtocol = DefaultApi()) { self.api = api super.init(frame: frame) foo() } required init?(coder aDecoder: NSCoder) { self.api = DefaultApi() super.init(coder: aDecoder) } // 现在调用注入的api实例的方法,而不是self的实现 public func foo() -> String { return api.stubMePlease() } } // 专门的默认实现类,用于生产环境 class DefaultApi: ApiProtocol {}
第二步:测试中用通用Mock类快速配置返回值
我们写一个通用的Mock类,初始化时就能指定返回值,这样每个测试用例可以轻松生成符合要求的Mock实例:
import XCTest class MyUIViewTests: XCTestCase { // 通用Mock类,支持自定义返回值 class MockApi: ApiProtocol { private let stubResult: String init(stubResult: String) { self.stubResult = stubResult } func stubMePlease() -> String { return stubResult } } override func setUp() { super.setUp() // 这里不再统一初始化,每个测试用例自己创建带指定Mock的实例 } func testA() { let myview = MyUIView(frame: .zero, api: MockApi(stubResult: "hello")) XCTAssertEqual(myview.foo(), "hello") } func testB() { let myview = MyUIView(frame: .zero, api: MockApi(stubResult: "weekend")) XCTAssertEqual(myview.foo(), "weekend") } func testC() { let myview = MyUIView(frame: .zero, api: MockApi(stubResult: "swift")) XCTAssertEqual(myview.foo(), "swift") } }
为什么这个方案最优?
- 解耦彻底:把业务逻辑和UI组件分开,符合单一职责原则,后续维护更方便
- 测试独立:每个测试用例的Mock配置互不干扰,不会出现测试污染
- 生产无侵入:默认初始化逻辑和原来完全一致,不需要修改任何生产代码的调用方式
- 扩展性强:以后
ApiProtocol加新方法,只需要更新Mock类,测试逻辑不用大改
备选临时方案:子类Mock(不推荐)
如果暂时不想重构依赖注入,也可以给MyUIView写子类重写方法,但这种方式代码冗余,耦合度高,只适合临时场景:
func testA() { class MockView: MyUIView { override func stubMePlease() -> String { return "hello" } } let myview = MockView() XCTAssertEqual(myview.foo(), "hello") }
但每个测试都要写一个子类,代码重复率高,而且如果MyUIView初始化逻辑复杂,子类可能会引入额外问题,所以还是依赖注入的方案更靠谱。
内容的提问来源于stack exchange,提问作者Klaus Jakobsen
相关产品推荐
相关产品推荐

