I have a Swift protocol that defines a generic function for making network requests. It looks something like this:
protocol TaskManagerProtocol {
func run<V>(
task: Task<V>,
completion: @escaping (Result<V, Error>) -> Void
)
}
This works great in production code, letting me make requests that receive different types of results. So far, so good.
The problem: For unit testing, I want a Test Spy that captures the arguments, especially the closure. The spy looks like this:
class TaskManagerSpy<Value>: TaskManagerProtocol {
var callCount = 0
var task: [Task<Value>] = []
var completion: [(Result<Value, Error>) -> Void] = []
func run<V>(
task: Task<V>,
completion: @escaping (Result<V, Error>) -> Void
) {
guard V.self == Value.self else {
fatalError("run<V> doesn't match init<Value>")
}
callCount += 1
self.task.append(task)
self.completion.append(completion)
}
}
My intention is to have test code instantiate TaskManagerSpy<SomeType>
and inject that into the System Under Test, which calls run<V>(task:completion:)
. But as it stands, the append()
calls fail to compile:
Cannot convert value of type 'Task<V>' to expected argument type 'Task<Value>'
and the same for the closure. If I comment out these lines, the test succeeds without triggering the fatal error.
Question: I have demonstrated that the types are the same. Is there a way to coerce one type into the other, so that I can capture the arguments?
Since, as you said, you've already asserted V.self == Value.self
, you ought to be able to force cast task
:
self.task.append(task as! Task<Value>)
Same thing for completion
:
self.completion.append(completion as! ((Result<Value, Error>) -> Void))