There are many reasons you might want to mock a network response:
- You don't want to test with network calls on production server
- The backend side of your team might not be ready yet
- There might be hard-to-reproduce cases such as token expired, wrong password, ...
Making a fake network call might also be useful for running unit testing, or creating a workable iOS app (which is not possible with localhost
)
Mock URLProtocol is a technique where you overrides initialization of URLSession
to make it return your own networking calls:
let urlSession = URLSession(configuration: configuration)
Creating a MockURLProtocol
class that can be passed to .protocolClasses
of URLSessionConfiguration
is where we headed
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
let urlSession = URLSession(configuration: configuration)
urlSession.dataTask(with: urlRequest) { data, response, error in
// ... handle your response
}.resume()
Implementing MockURLProtocol
as suggested by Apple in WWDC 2018:
class MockURLProtocol: URLProtocol {
static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data) )?
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func stopLoading() {
}
override func startLoading() {
guard let handler = MockURLProtocol.requestHandler else {
return
}
do {
let (response, data) = try handler(request)
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
client?.urlProtocolDidFinishLoading(self)
} catch {
client?.urlProtocol(self, didFailWithError: error)
}
}
}
Passing your response inside .requestHandler
:
MockURLProtocol.requestHandler = {request in
let exampleData = """
{
"base": "EUR",
"date": "2018-04-08",
"rates": {
"CAD": 1.565,
"CHF": 1.1798,
"GBP": 0.87295,
"SEK": 10.2983,
"EUR": 1.092,
"USD": 1.2234
}
}
""".data(using: .utf8)!
let response = HTTPURLResponse.init(url: request.url!, statusCode: 200, httpVersion: "2.0", headerFields: nil)!
return (response, exampleData)
}
(an example response from exchangeratesapi)
You can custom MockUrlProtocol
above by
- subclasing
MockURLProcotol
for each type of request, just overridingstartLoading
and adding your own.requestHandler
:
class MockUnclaimedTokenExpire: MockURLProtocol {
override func startLoading() {
MockUnclaimedTokenExpire.requestHandler = { request in
// new custom response
}
super.startLoading()
}
}
- Different return result based on handling
request
With the builtin tools above, you can complete a workable app despite backend is ready or not. Just remember to switch back to URLSession.shared
when you're ready to deploy in production.
Top comments (0)