依赖注入:静态方法和参数

时间:2018-04-27 16:15:02

标签: swift unit-testing dependency-injection

我正在尝试将单元测试添加到在Objective-C库上分层的Swift程序。我目前的主要问题是找到一种方法来注入使用参数化静态工厂方法创建的依赖项。

例如,以下代码是功能性的,但是耐测试:

class Processor {
    var service: RegistrationService?

    func register(user: String, pass: String) {
        let configuration = Configuration(user: user, pass: pass)
        service = RegistrationServiceProvider.registrationService(configuration: configuration)

        // Do various things with the 'service'
    }
}

请注意,RegistrationServiceProviderRegistrationServiceConfiguration都来自Objective-C库。

我希望能够做的是提供在此代码中创建的RegistrationService作为默认值,并在测试时将其替换为我自己的模拟。没有Configuration对象,使用像http://www.danielhall.io/swift-y-dependency-injection-part-two这样的东西会非常简单。

(我意识到我可以/应该将Configuration构造推送给调用者,但这并不能解决如何将其提供给默认服务的问题。)

欢迎提出建议和参考。

2 个答案:

答案 0 :(得分:1)

您可以创建RegistrationService和RegistrationServiceProvider的模拟并在测试中注入它们,使用标准Type作为普通调用中的默认类型,如下面的代码(它包括您使用的类的示例版本和一些打印输出以查看所谓的内容:

class Configuration {
    let user: String
    let pass: String

    init(user: String, pass: String) {
        self.user = user
        self.pass = pass
    }
}

class RegistrationService {
    let configuration: Configuration

    init(configuration: Configuration) {
        self.configuration = configuration
    }
}

class RegistrationServiceProvider {

    class func registrationService(configuration: Configuration) -> RegistrationService {
        print("Provider instantiated service")
        return RegistrationService(configuration: configuration)
    }
}

class Processor {
    var service: RegistrationService?
    func register(user: String, pass: String, serviceProvider: RegistrationServiceProvider.Type = RegistrationServiceProvider.self) {
        let configuration = Configuration(user: user, pass: pass)
        service = serviceProvider.registrationService(configuration: configuration)

        // Do various things with the 'service'
    }
}

class MockProvider: RegistrationServiceProvider {
    override class func registrationService(configuration: Configuration) -> RegistrationService {
        print("Mock provider instantiated mock service")
        return MockService(configuration: configuration)
    }
}

class MockService: RegistrationService {
    override init(configuration: Configuration) {
        super.init(configuration: configuration)
        print("Mock service initialized")
    }
}

let processor = Processor()

processor.register(user: "userName", pass: "myPassword") // Provider instantiated service

processor.register(user: "userName", pass: "myPassword", serviceProvider: MockProvider.self) // Mock provider instantiated mock service

答案 1 :(得分:0)

我最终使用协议而不是继承,实现了与上面接受的答案略有不同的实现。我认为这不是更好的实现。只是个人喜好。它确实需要额外的代码来定义协议。

protocol ServiceProvidable {
   func registrationService(configuration: Configuration) -> RegistrationService
}

这会将register函数的参数更改为...

func register(user: String, pass: String, serviceProvider: ServiceProvidable.Type = RegistrationServiceProvider.self)

并且提供者将遵守协议...

class RegistrationServiceProvider: ServiceProvidable

现在,您的模拟提供程序可以简单地遵守协议并实现所需的功能,而无需覆盖。您还可以获得让Xcode存根该函数的额外好处。没什么大不了,但带来的便利却很小。

与面向协议的编程风格不同。