Swift单元测试:基于Objective-C库的参数化静态工厂依赖注入问题
嘿,这个场景我在Swift和Objective-C混编的项目里太熟悉了!你现在的问题核心就是静态工厂方法把依赖创建和业务逻辑硬绑在了一起,导致没法在测试时替换成Mock对象来验证行为对吧?下面给你几个实用的解决方案,按推荐程度排序:
1. 构造函数注入(最推荐,符合依赖倒置原则)
这个方案的核心是把RegistrationService的创建权从Processor内部转移到外部,通过构造函数传入。为了能替换成Mock,我们需要先给Objective-C的RegistrationService定义一个Swift协议:
// 定义协议,包含业务需要用到的方法 @objc protocol RegistrationServicing { func completeRegistration() -> Bool // 把你实际需要调用的OC类方法都加到这里 } // 让Objective-C的RegistrationService遵守这个协议(Swift扩展里就能实现) extension RegistrationService: RegistrationServicing {}
然后修改Processor的构造函数,直接注入依赖:
class Processor { private let service: RegistrationServicing // 构造函数注入,外部决定传入真实服务还是Mock init(service: RegistrationServicing) { self.service = service } func register(user: String, pass: String) { // 如果Configuration是service需要的,可以在这里传给service(比如加个configure方法) service.configure(with: Configuration(user: user, pass: pass)) service.completeRegistration() } }
测试的时候,你只需要创建一个Mock类实现协议:
class MockRegistrationService: RegistrationServicing { var didCallRegister = false var receivedConfiguration: Configuration? func configure(with configuration: Configuration) { receivedConfiguration = configuration } func completeRegistration() -> Bool { didCallRegister = true return true } } // 测试用例示例 func testRegistration() { let mockService = MockRegistrationService() let processor = Processor(service: mockService) processor.register(user: "testUser", pass: "testPass") XCTAssertTrue(mockService.didCallRegister) XCTAssertEqual(mockService.receivedConfiguration?.user, "testUser") }
2. 属性注入(适合不想大幅修改现有代码的场景)
如果不想改构造函数,可以把service改成可变属性,默认用静态工厂初始化,测试时替换成Mock:
class Processor { // 默认用真实工厂创建,测试时可以直接替换 var service: RegistrationServicing = { // 这里可以用默认配置,或者留空,在register里再配置 let defaultConfig = Configuration(user: "", pass: "") return RegistrationServiceProvider.registrationService(with: defaultConfig) }() func register(user: String, pass: String) { let config = Configuration(user: user, pass: pass) service.configure(with: config) service.completeRegistration() } } // 测试时直接替换属性 func testRegistration() { let processor = Processor() let mockService = MockRegistrationService() processor.service = mockService processor.register(user: "testUser", pass: "testPass") XCTAssertTrue(mockService.didCallRegister) }
3. 封装工厂方法(适合需要保留工厂创建逻辑的场景)
如果不想完全去掉静态工厂的使用,可以把工厂本身也抽象成协议,这样测试时可以替换成Mock工厂:
// 定义工厂协议 @objc protocol RegistrationServiceProviding { func makeRegistrationService(with config: Configuration) -> RegistrationServicing } // 默认实现用原有的静态工厂 class DefaultServiceProvider: RegistrationServiceProviding { func makeRegistrationService(with config: Configuration) -> RegistrationServicing { return RegistrationServiceProvider.registrationService(with: config) } } class Processor { private let provider: RegistrationServiceProviding // 默认用真实工厂,测试时传Mock工厂 init(provider: RegistrationServiceProviding = DefaultServiceProvider()) { self.provider = provider } func register(user: String, pass: String) { let config = Configuration(user: user, pass: pass) let service = provider.makeRegistrationService(with: config) service.completeRegistration() } } // 测试用的Mock工厂 class MockServiceProvider: RegistrationServiceProviding { let mockService = MockRegistrationService() func makeRegistrationService(with config: Configuration) -> RegistrationServicing { // 可以在这里验证配置是否正确 XCTAssertEqual(config.user, "testUser") return mockService } } // 测试用例 func testRegistration() { let mockProvider = MockServiceProvider() let processor = Processor(provider: mockProvider) processor.register(user: "testUser", pass: "testPass") XCTAssertTrue(mockProvider.mockService.didCallRegister) }
注意事项
因为依赖是Objective-C库的类,所以定义Swift协议时要加上@objc关键字,确保协议方法能和OC类兼容。如果OC类的方法有特殊参数或返回值,也要保证协议里的定义完全匹配。
内容的提问来源于stack exchange,提问作者Phillip Mills
相关产品推荐
相关产品推荐

