Wednesday, 2 October 2019

Creating web service call in more testable manner



Mostly we have a class like this to manage the network calls, nothing wrong in it, it will serve the purpose in elegant way.

class NetworkClient { enum Result { case data(Data) case error(Error) } func load(from url: URL, completionHandler: @escaping (Result) -> Void) { let task = URLSession.shared.dataTask(with: url) { (data, response, error) in if let error = error { return completionHandler(.error(error)) } completionHandler(.data(data ?? Data())) } task.resume() } }

but, writing test case for this kind of system provided singleton is not that much easier job.

we need to follow certain steps to make sure our things is working as we are expecting


These are the steps

1. Abstract into a protocol
2. Use the protocol with the singleton as the default
3. Mock the protocol in your tests

protocol NetworkEngine { typealias Handler = (Data?, URLResponse?, Error?) -> Void func performRequest(for url: URL, completionHandler: @escaping Handler) } extension URLSession: NetworkEngine { typealias Handler = NetworkEngine.Handler

func performRequest(for url: URL, completionHandler: @escaping Handler) { let task = dataTask(with: url, completionHandler: completionHandler) task.resume() } }


note: Keep it in constructor based injection to mock the data easily

class NetworkClient { .
.
.
private let engine: NetworkEngine init(engine: NetworkEngine = URLSession.shared) { self.engine = engine } func load(from url: URL, completionHandler: @escaping (Result) -> Void) { engine.performRequest(for: url) { (data, response, error) in if let error = error { return completionHandler(.error(error)) } completionHandler(.data(data ?? Data())) } } }

And testing would be made easily like this

func testLoadingData() { class NetworkEngineMock: NetworkEngine { typealias Handler = NetworkEngine.Handler var requestedURL: URL? func performRequest(for url: URL, completionHandler: @escaping Handler) { requestedURL = url let data = “Hello world”.data(using: .utf8) completionHandler(data, nil, nil) } } let engine = NetworkEngineMock() let loader = DataLoader(engine: engine) var result: DataLoader.Result? let url = URL(string: “my/API”)! loader.load(from: url) { result = $0 } XCTAssertEqual(engine.requestedURL, url) XCTAssertEqual(result, .data(“Hello world”.data(using: .utf8)!)) }