DEV Community

Quang
Quang

Posted on • Edited on

2 2

Mock URLProtocol for local unit testing

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 overriding startLoading 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.

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay