Created
May 3, 2023 16:53
-
-
Save jayesh15111988/ee13a62080433b931b55570db5d5c209 to your computer and use it in GitHub Desktop.
A full source code for tests for network stack on iOS and Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// JSONDataReader.swift | |
import XCTest | |
final class JSONDataReader { | |
static func getDataFromJSONFile(with name: String) -> Data? { | |
guard let pathString = Bundle(for: self).path(forResource: name, ofType: "json") else { | |
XCTFail("Mock JSON file \(name).json not found") | |
return nil | |
} | |
guard let jsonString = try? String(contentsOfFile: pathString, encoding: .utf8) else { | |
return nil | |
} | |
guard let jsonData = jsonString.data(using: .utf8) else { | |
return nil | |
} | |
return jsonData | |
} | |
} | |
//URLProtocolMock.swift | |
import Foundation | |
// Referenced from: https://forums.raywenderlich.com/t/chapter-8-init-deprecated-in-ios-13/102050/6 | |
class URLProtocolMock: URLProtocol { | |
static var mockURLs = [URL?: (error: Error?, data: Data?, response: HTTPURLResponse?)]() | |
override class func canInit(with request: URLRequest) -> Bool { | |
return true | |
} | |
override class func canonicalRequest(for request: URLRequest) -> URLRequest { | |
return request | |
} | |
override func startLoading() { | |
if let url = request.url { | |
if let (error, data, response) = URLProtocolMock.mockURLs[url] { | |
if let responseStrong = response { | |
self.client?.urlProtocol(self, didReceive: responseStrong, cacheStoragePolicy: .notAllowed) | |
} | |
if let dataStrong = data { | |
self.client?.urlProtocol(self, didLoad: dataStrong) | |
} | |
if let errorStrong = error { | |
self.client?.urlProtocol(self, didFailWithError: errorStrong) | |
} | |
} | |
} | |
DispatchQueue.main.async { | |
self.client?.urlProtocolDidFinishLoading(self) | |
} | |
} | |
override func stopLoading() { | |
// no-op | |
} | |
} | |
// RequestHandlerTests.swift | |
@testable import CataPoke | |
import XCTest | |
final class RequestHandlerTests: XCTestCase { | |
var mockSession: URLSession! | |
let validResponse = HTTPURLResponse(url: URL(string: "https://something.com")!, statusCode: 200, httpVersion: nil, headerFields: nil) | |
let invalidResponse = HTTPURLResponse(url: URL(string: "https://something.com")!, statusCode: 400, httpVersion: nil, headerFields: nil) | |
var networkService: RequestHandler! | |
override func setUp() { | |
super.setUp() | |
setupURLProtocolMock() | |
networkService = RequestHandler(urlSession: mockSession) | |
} | |
override func tearDown() { | |
super.tearDown() | |
URLProtocolMock.mockURLs = [:] | |
} | |
func testThatNetworkServiceCorrectlyHandlesValidResponse() { | |
let urlWithValidData = "https://valid_data.url" | |
let validData = JSONDataReader.getDataFromJSONFile(with: "species") | |
URLProtocolMock.mockURLs = [ | |
URL(string: urlWithValidData): (nil, validData, validResponse) | |
] | |
let expectation = XCTestExpectation(description: "Successful JSON to model conversion while loading valid data from API") | |
networkService.request(type: SpeciesResponse.self, route: .getSpecies(URL(string: urlWithValidData)!)) { result in | |
if case .success = result { | |
// No-op. If we reached here, that means we passed the test | |
} else { | |
XCTFail("Test failed. Expected to get the valid data without any error. Failed due to unexpected result") | |
} | |
expectation.fulfill() | |
} | |
wait(for: [expectation], timeout: 2.0) | |
} | |
func testThatNetworkServiceCorrectlyHandlesResponseWithError() { | |
let urlWithError = "https://random_error" | |
URLProtocolMock.mockURLs = [ | |
URL(string: urlWithError): (DataLoadError.genericError("Something went wrong"), nil, validResponse) | |
] | |
let expectation = XCTestExpectation(description: "Unsuccessful data load operation due to generic error data") | |
networkService.request(type: SpeciesResponse.self, route: .getSpecies(URL(string: urlWithError)!)) { result in | |
if case .failure = result { | |
// No-op. If we reached here, that means we passed the test | |
} else { | |
XCTFail("Test failed. Expected to get the DataLoadError with type genericError with error message. Failed due to unexpected result") | |
} | |
expectation.fulfill() | |
} | |
wait(for: [expectation], timeout: 2.0) | |
} | |
func testThatNetworkServiceCorrectlyHandlesResponseWithInvalidResponseCode() { | |
let urlWithInvalidResponse = "https://invalid_response" | |
let invalidResponse = HTTPURLResponse(url: URL(string: "https://github.com")!, statusCode: 400, httpVersion: nil, headerFields: nil) | |
let invalidData = JSONDataReader.getDataFromJSONFile(with: "species_invalid") | |
URLProtocolMock.mockURLs = [ | |
URL(string: urlWithInvalidResponse): (nil, invalidData, invalidResponse) | |
] | |
let expectation = XCTestExpectation(description: "Unsuccessful data load operation due to invalid response code") | |
networkService.request(type: SpeciesResponse.self, route: .getSpecies(URL(string: urlWithInvalidResponse)!)) { result in | |
if case .failure(.invalidResponseCode(let code)) = result { | |
XCTAssertEqual(code, 400) | |
} else { | |
XCTFail("Test failed. Expected to get the DataLoadError with type invalidResponseCode with error message. Failed due to unexpected result") | |
} | |
expectation.fulfill() | |
} | |
wait(for: [expectation], timeout: 2.0) | |
} | |
// MARK: Private methods | |
private func setupURLProtocolMock() { | |
let sessionConfiguration = URLSessionConfiguration.ephemeral | |
sessionConfiguration.protocolClasses = [URLProtocolMock.self] | |
mockSession = URLSession(configuration: sessionConfiguration) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment