import Foundation extension URL { public var queryParameters: [String: String]? { guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else { return nil } return queryItems.reduce(into: [String: String]()) { (result, item) in result[item.name] = item.value } } } extension URLRequest { func bodySteamAsJSON() -> [String: Any]? { guard let bodyStream = self.httpBodyStream else { return nil } bodyStream.open() // Will read 16 chars per iteration. Can use bigger buffer if needed let bufferSize: Int = 16 let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) var dat = Data() while bodyStream.hasBytesAvailable { let readDat = bodyStream.read(buffer, maxLength: bufferSize) dat.append(buffer, count: readDat) } buffer.deallocate() bodyStream.close() return try? JSONSerialization.jsonObject(with: dat, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any] } } class MockURLProtocol: URLProtocol { static var baseUrl: String = "" static var apiMethodMocks: [ApiMethodMockProtocol] = [] override class func canInit(with request: URLRequest) -> Bool { return true } override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } override func startLoading() { guard let requestUrl = request.url else { return } let methodMock = MockURLProtocol.apiMethodMocks.first { return request.url?.absoluteString == MockURLProtocol.baseUrl + $0.path && request.httpMethod == $0.httpMethod } guard let methodMock = methodMock else { if let response = HTTPURLResponse(url: requestUrl, statusCode: 404, httpVersion: "HTTP/2", headerFields: [:]) { client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocolDidFinishLoading(self) } return } // Assuming we use url parameters in GET requests and JSON-encoded body in everything else var params: [String: Any] = [:] if request.httpBodyStream != nil { if let bodyDict = request.bodySteamAsJSON() { params = bodyDict } } else { if let urlParams = requestUrl.queryParameters { params = urlParams } } let result = methodMock.response(headers: request.allHTTPHeaderFields ?? [:], params: params) guard let response = HTTPURLResponse(url: requestUrl, statusCode: result.status, httpVersion: "HTTP/2", headerFields: [:]) else { return } client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) if let data = result.data { client?.urlProtocol(self, didLoad: data) } client?.urlProtocolDidFinishLoading(self) //client?.urlProtocol(self, didFailWithError: error) } override func stopLoading() { } }