Reputation: 31016
I am attempting to add unit tests to a Swift program that is layered on an Objective-C library. My main problem at the moment is finding a way to inject a dependency that is created using a parameterized static factory method.
As an example, the following code is functional but rather test-resistant:
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'
}
}
Note that RegistrationServiceProvider
, RegistrationService
, and Configuration
are all from the Objective-C library.
What I'd like to be able to do is to provide the RegistrationService
that's created in this code as the default and replace it with my own mock when testing. Without the Configuration
object that would be fairly simple using something like http://www.danielhall.io/swift-y-dependency-injection-part-two.
(I realize that I could/should push the Configuration
construction to the caller, but that doesn't solve the problem of how to supply it to a default service.)
Suggestions and references are welcome.
Upvotes: 2
Views: 2011
Reputation: 706
I ended up with a slightly different implementation than the accepted answer above using a protocol instead of inheritance. I don't believe it is a better implementation. Just a personal preference. It does require extra code to define the protocol.
protocol ServiceProvidable {
func registrationService(configuration: Configuration) -> RegistrationService
}
That would change the parameters of the register
function to...
func register(user: String, pass: String, serviceProvider: ServiceProvidable.Type = RegistrationServiceProvider.self)
and the provider would conform to the protocol...
class RegistrationServiceProvider: ServiceProvidable
Now your mock providers could simply adhere to the protocol and implement the required function without an override. You also get the added benefit of letting Xcode stub out the function. Not a big deal but a small convenience.
Just a different perspective from a protocol oriented programming style.
Upvotes: 0
Reputation: 1457
You can create a mock of both RegistrationService and RegistrationServiceProvider and inject them in the test, using the standard Type as the default type in the normal call, like the code below (it includes example versions of the classes that you use and some printouts to see what is called):
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
Upvotes: 6