Mock URLProtocol for local unit testing
Tags: ios, swift, urlsession, networking
Published: 2019-11-27 06:55:17

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:

```swift
let urlSession = URLSession(configuration: configuration)
```

Creating a `MockURLProtocol` class that can be passed to `.protocolClasses` of `URLSessionConfiguration` is where we headed

```swift
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](https://developer.apple.com/videos/play/wwdc2018/417/):

```swift
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`:

```swift
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](https://exchangeratesapi.io/))

 You can custom `MockUrlProtocol` above by 
 - subclasing `MockURLProcotol` for each type of request, just overriding `startLoading` and adding your own `.requestHandler`:

```swift
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.