Skip to content

Commit

Permalink
Improve HttpClient unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dogo committed Aug 23, 2024
1 parent 3c1ffb6 commit aa612e7
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 38 deletions.
36 changes: 24 additions & 12 deletions SWDestinyTradesTests/Doubles/URLSessionMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class URLSessionMock {

final class URLProtocolMock: URLProtocol {

static var response: ((URLRequest) throws -> (HTTPResponse?))?
static var response: ((URLRequest) throws -> HTTPResponse?)?

override class func canInit(with request: URLRequest) -> Bool {
return true
Expand All @@ -32,18 +32,23 @@ final class URLProtocolMock: URLProtocol {
override func startLoading() {

do {
guard var urlRequest = try Self.response?(request) else {
client?.urlProtocol(self, didFailWithError: NSError(domain: "MyErrorDomain",
code: 1,
userInfo: ["reason": "Response creation failed"]))
guard let httpResponse = try Self.response?(request) else {
client?.urlProtocol(self, didFailWithError: NSError(domain: "MockErrorDomain",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "No response provided"]))
return
}

if let response = urlRequest.response {
if let response = httpResponse.response {
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
} else {
client?.urlProtocol(self, didFailWithError: NSError(domain: "MockErrorDomain",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Invalid HTTP response"]))
return
}

if let data = urlRequest.data {
if let data = httpResponse.data {
client?.urlProtocol(self, didLoad: data)
}
client?.urlProtocolDidFinishLoading(self)
Expand All @@ -58,13 +63,20 @@ final class URLProtocolMock: URLProtocol {
struct HTTPResponse {

var data: Data?
let statusCode: Int
var response: URLResponse?

lazy var response: HTTPURLResponse? = {
let response = HTTPURLResponse(url: URL(string: "http://base.url.com")!,
init(data: Data?, statusCode: Int, isHTTP: Bool = true) {
if isHTTP {
response = HTTPURLResponse(url: URL(string: "http://base.url.com")!,
statusCode: statusCode,
httpVersion: nil,
headerFields: nil)
return response
}()
} else {
response = URLResponse(url: URL(string: "http://base.url.com")!,
mimeType: nil,
expectedContentLength: 0,
textEncodingName: nil)
}
self.data = data
}
}
58 changes: 32 additions & 26 deletions SWDestinyTradesTests/Rest/HttpClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,73 +25,79 @@ final class HttpClientTests: XCTestCase {
request.httpMethod = HttpMethod.get.toString()
}

func testRequestWithSuccess() async throws {
func test_request_with_success() async throws {
URLProtocolMock.response = { _ in
HTTPResponse(data: "{ \"bar\": true }".data(using: .utf8),
HTTPResponse(data: Data("{ \"bar\": true }".utf8),
statusCode: 200)
}

let result = try await sut.request(request, decode: Foo.self)
XCTAssertTrue(result.bar)
}

func testRequestWithFailureInvalidData() async throws {
func test_request_with_failure_invalidData() async throws {
let url = URL(string: "https://example.com")!
let request = URLRequest(url: url)

URLProtocolMock.response = { _ in
HTTPResponse(statusCode: 200)
return HTTPResponse(data: nil, statusCode: 200, isHTTP: false)
}

do {
_ = try await sut.request(request, decode: Foo.self)
XCTFail("Expected to throw while awaiting, but succeeded")
XCTFail("Expected to throw APIError.invalidData, but succeeded.")
} catch {
// XCTAssertEqual(error as? APIError, .invalidData)
XCTAssertEqual(error as? APIError, .invalidData, "Expected APIError.invalidData but got \(error)")
}
}

func testRequestWithFailureResponseUnsuccessful() async throws {
func test_request_with_failure_responseUnsuccessful() async throws {
URLProtocolMock.response = { _ in
HTTPResponse(statusCode: 404)
HTTPResponse(data: nil, statusCode: 404)
}

do {
_ = try await sut.request(request, decode: Foo.self)
XCTFail("Expected to throw while awaiting, but succeeded")
} catch {
XCTAssertEqual(error as? APIError, .responseUnsuccessful)
XCTAssertEqual(error as? APIError, .responseUnsuccessful, "Expected APIError.responseUnsuccessful but got \(error)")
}
}

func testRequestWithFailureRequestCancelled() async throws {
func test_request_with_failure_requestCancelled() async throws {
request = URLRequest(with: URL(string: "https://base.url.com")!)

URLProtocolMock.response = { _ in
HTTPResponse(statusCode: 200)
throw URLError(.cancelled)
}

do {
_ = try await sut.request(request, decode: Foo.self)
XCTFail("Expected to throw while awaiting, but succeeded")
XCTFail("Expected to throw APIError.requestCancelled, but succeeded.")
} catch {
// XCTAssertEqual(error as? APIError, .requestCancelled)
XCTAssertEqual(error as? APIError, .requestCancelled, "Expected APIError.requestCancelled but got \(error)")
}
}

func testRequestWithFailureKeyNotFound() async throws {
func test_request_with_failure_keyNotFound() async throws {
URLProtocolMock.response = { _ in
HTTPResponse(data: "{ \"id\": 3465 }".data(using: .utf8),
HTTPResponse(data: Data("{ \"id\": 3465 }".utf8),
statusCode: 200)
}

do {
_ = try await sut.request(request, decode: Foo.self)
XCTFail("Expected to throw while awaiting, but succeeded")
} catch let error as APIError {
XCTAssertEqual(error, .keyNotFound(key: Foo.CodingKeys.bar,
context: "No value associated with key CodingKeys(stringValue: \"bar\", intValue: nil) (\"bar\")."))
} catch {
XCTAssertEqual(error as? APIError,
.keyNotFound(key: Foo.CodingKeys.bar, context: "No value associated with key CodingKeys(stringValue: \"bar\", intValue: nil) (\"bar\")."),
"Expected APIError.requestCancelled but got \(error)")
}
}

func testRequestWithFailureValueNotFound() async throws {
func test_request_with_failure_valueNotFound() async throws {
URLProtocolMock.response = { _ in
HTTPResponse(data: "{ \"bar\": \"invalid_value\" }".data(using: .utf8),
HTTPResponse(data: Data("{ \"bar\": \"invalid_value\" }".utf8),
statusCode: 200)
}

Expand All @@ -103,9 +109,9 @@ final class HttpClientTests: XCTestCase {
}
}

func testRequestWithFailureTypeMismatch() async throws {
func test_request_with_failure_typeMismatch() async throws {
URLProtocolMock.response = { _ in
HTTPResponse(data: "{ \"bar\": \"invalid_value\" }".data(using: .utf8),
HTTPResponse(data: Data("{ \"bar\": \"invalid_value\" }".utf8),
statusCode: 200)
}

Expand All @@ -117,20 +123,20 @@ final class HttpClientTests: XCTestCase {
}
}

func testRequestWithFailureDataCorrupted() async throws {
func test_request_with_failure_dataCorrupted() async throws {
URLProtocolMock.response = { _ in
HTTPResponse(statusCode: 200)
HTTPResponse(data: nil, statusCode: 200)
}

do {
_ = try await sut.request(request, decode: Foo.self)
XCTFail("Expected to throw while awaiting, but succeeded")
} catch {
XCTAssertEqual(error as? APIError, .dataCorrupted(context: "The given data was not valid JSON."))
XCTAssertEqual(error as? APIError, .dataCorrupted(context: "The given data was not valid JSON."), "Expected APIError.dataCorrupted but got \(error)")
}
}

func testCancelAllRequests() {
func test_cancelRequest() {
request = URLRequest(with: URL(string: "https://base.url.com")!)
sut.cancelRequest(request)
// Uncomment once cancellation handling is implemented in the HttpClient
Expand Down

0 comments on commit aa612e7

Please sign in to comment.