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)