I'm working on an app which needs to query multiple APIs. I've come up with classes for each API provider (and in more extreme cases, a class for each specific API Endpoint). This is because each API query is expected to return a very strict type of response, so if an API can, for instance, return both user profiles and profile pictures, I only want a response to be specific to either of those.
I've implemented it roughly in the following manner:
protocol MicroserviceProvider {
associatedtype Response
}
protocol ProfilePictureMicroserviceProvider: MicroserviceProvider {
func getPicture(by email: String, _ completion: (Response) -> Void)
}
class SomeProfilePictureAPI: ProfilePictureMicroserviceProvider {
struct Response {
let error: Error?
let picture: UIImage?
}
func getPicture(by email: String, _ completion: (Response) -> Void) {
// some HTTP magic
// will eventually call completion(_:) with a Response object
// which either holds an error or a UIImage.
}
}
Because I want to be able to Unit Test classes that will rely on this API, I need to be able to inject that profile picture dependency dynamically. By default it will use SomeProfilePictureAPI
but when running tests I will be able to replace that with a MockProfilePictureAPI
which will still adhere to ProfilePictureMicroserviceProvider
.
And because I'm using associated types, I need to make classes that depend on ProfilePictureMicroserviceProvider
generic.
At first, I naively did try to write my view controller like such
class SomeClass {
var profilePicProvider: ProfilePictureMicroserviceProvider
}
But that just led the frustratingly famous 'Protocol ProfilePictureMicroserviceProvider can only be used as a generic constraint because it has Self or associated type requirements' compile-time error.
Now I've been reading up on the issue over the last couple days, trying to wrap my head around Protocols with Associated Types (PATS), and figured I'd take the route of generic classes like such:
class SomeClass<T: ProfilePictureMicroserviceProvider> {
var profilePicProfider: T = SomeProfilePictureAPI()
}
But even then I get the following error:
Cannot convert value of type 'SomeProfilePictureAPI' to specified type 'T'
Even though having T
being constrained to the ProfilePictureMicroserviceProvider
protocol, and having SomeProfilePictureAPI
adhere to it...
Basically the main idea was to reach 2 objectives: enforce Microservice structure with mandatory Response type, and make each Microservice mock-able for unit tests of dependent classes.
I'm now stuck with choosing either one of the two as I can't seem to make it work. Any help telling me what I'm doing wrong would be most welcome.
I've also had a look at type-erasure. But this to me seems very whacky and quite an effort for something that looks wrong on many aspects.
So basically my question is two-fold: how can I enforce my Microservices to define their own Response type ? And how can I easily replace them by mock microservices in classes that depend on them ?