I have been working on an interceptor for adding auth headers to network requests in my app.
final class AuthInterceptor: URLProtocol {
private var token: String = "my.access.token"
private var dataTask: URLSessionTask?
private struct UnexpectedValuesRepresentationError: Error { }
override class func canInit(with request: URLRequest) -> Bool {
guard URLProtocol.property(forKey: "is_handled", in: request) as? Bool == nil else { return false }
return true
//return false // URL Loading System will handle the request using the system’s default behavior
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
guard let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else { return }
URLProtocol.setProperty(true, forKey: "is_handled", in: mutableRequest)
mutableRequest.addValue(token, forHTTPHeaderField: "Authorization")
dataTask = URLSession.shared.dataTask(with: mutableRequest as URLRequest) { [weak self] data, response, error in
guard let self = self else { return }
if let error = error {
self.client?.urlProtocol(self, didFailWithError: error)
} else if let data = data, let response = response {
self.client?.urlProtocol(self, didLoad: data)
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
} else {
self.client?.urlProtocol(self, didFailWithError: UnexpectedValuesRepresentationError())
}
self.client?.urlProtocolDidFinishLoading(self)
}
dataTask?.resume()
}
override func stopLoading() {
dataTask?.cancel()
dataTask = nil
}
}
As you can see I am currently just using private var token: String = "my.access.token"
to mock a token. I'd like to introduce a TokenLoader
that will fetch my token from it's cache.
As the URL Loading system will initialize instances of my protocol as needed, I am not sure how I can inject this. It is currently registered using: URLProtocol.registerClass(AuthInterceptor.self)
Imagine I had an interface like this -
public typealias LoadTokenResult = Result<String, Error>
public protocol TokenLoader {
func load(_ key: String, completion: @escaping (LoadTokenResult) -> Void)
}
I'd like to ensure this is testable so I'd expect to be able to stub this in a test case or use a spy.
How can I achieve this?
As it is seen AuthInterceptor
does not depend on TokenProvider
by instance level, so you can inject class level dependency and use shared TokenProvider
to extract/load tokens (for instance, depending on URL). This gives possibility to inject cached token provider for testing easily
final class AuthInterceptor: URLProtocol {
static var tokenProvider: TokenLoader? // << inject here
// initial value, will be loaded from to tokenProvider result
private var token: String?
// ... other code
// somewhere inside when needed, like
Self.tokenProvider?.load(key) { [weak self] result in
self?.token = try? result.get()
// proceed with token if present ...
}
}
so in UT scenario you can do something like
override func setUp {
AuthInterceptor.tokenProvider = MockTokenProvider()
}
override func tearDown {
AuthInterceptor.tokenProvider = nil // or some default if needed
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With