Create Swift Package
In Xcode Menu, choose New -> Package from file menu.public class HttpClient { | |
let session: URLSession | |
public init(_ session: URLSession) { | |
self.session = session | |
} | |
public func performRequest(_ request: URLRequest) async throws -> (data: Data, response: URLResponse) { | |
try await session.data(for: request) | |
} | |
} |
platforms: [
.iOS(.v13),
.macOS(.v12)
]
To consume the session tasks, we need url requests, for example assume our app have a feature to fetch the contacts from server. public enum ContactEndPoint { | |
case myContacts | |
public func url(baseURL: URL) -> URLRequest { | |
switch self { | |
case .myContacts: | |
var request = URLRequest(url: baseURL.appendingPathComponent("/api/v1/users")) | |
request.httpMethod = "GET" | |
var requestHeaders = [String: String]() | |
requestHeaders["Content-Type"] = "application/json" | |
request.allHTTPHeaderFields = requestHeaders | |
return request | |
} | |
} | |
} |
public class ContactAPI { | |
let client: HttpClient | |
let baseURL: URL | |
public init(client: HttpClient, baseURL: URL ) { | |
self.client = client | |
self.baseURL = baseURL | |
} | |
public func getMyContacts() async throws { | |
let contactRequest = ContactEndPoint.myContacts.url(baseURL: baseURL) | |
try await client.performRequest(contactRequest) | |
} | |
} |
public struct Contact: Decodable { | |
let contact_ID: Int | |
} | |
enum ContactDataMapper { | |
static func map(_ data: Data, from response: URLResponse) throws -> [Contact] { | |
guard let contacts = try? JSONDecoder().decode([Contact].self, from: data) else { | |
throw APIError.invalidData | |
} | |
return contacts | |
} | |
} | |
public enum APIError: Swift.Error { | |
case invalidData | |
case serverDefined(String) | |
case connectivity | |
} |
Also modify the getMyContacts() func.public func getMyContacts() async throws -> [Contact] {
let contactRequest = ContactEndPoint.myContacts.url(baseURL: baseURL)
let (data, httpResponse) = try await client.performRequest(contactRequest)
return try ContactDataMapper.map(data, from: httpResponse)
}
Create New Unit Test Case
Create protocol HTTPURLSession
And in the HttpClient class, change the type of session variable as HTTPURLSession.public protocol HTTPURLSession {
func data(for request: URLRequest) async throws -> (data: Data, response: HTTPURLResponse)
}
func test_init_doesNotExecuteURLRequest() async {
let (_, session) = makeSUT()
let executedUrls = session.executedURLs
XCTAssertTrue(executedUrls.isEmpty)
}
func test_deliverConnectivityErrorOnClientError() async throws {
let (sut, _) = makeSUT()
do {
_ = try await sut.getMyContacts()
} catch let error {
XCTAssertEqual((error as? APIError), APIError.connectivity)
return
}
XCTFail()
}
func test_deliverErrorOnInvalidJSONWith200Status() async throws {
let url = URL(string: "https://aurl.com")!
let (sut, session) = makeSUT(url: url)
let myContactURL = ContactEndPoint.myContacts.url(baseURL: url).url!
let invalidJSON = """
[
{"contactsssss_ID" : 2 }
]
"""
session.setResponse((invalidJSON.data(using: .utf8)!, responseWithStatusCode(200, url: myContactURL)), for: myContactURL)
do {
_ = try await sut.getMyContacts()
} catch let error {
XCTAssertEqual((error as? APIError), APIError.invalidData)
return
}
XCTFail()
}
func test_load_DeliverErroFor400Status() async throws {
let url = URL(string: "https://aurl.com")!
let (sut, session) = makeSUT(url: url)
let myContactURL = ContactEndPoint.myContacts.url(baseURL: url).url!
session.setResponse(("".data(using: .utf8)!, responseWithStatusCode(400, url: url)), for: myContactURL)
do {
_ = try await sut.getMyContacts()
} catch let error {
XCTAssertEqual((error as? APIError), APIError.serverDefined("400"))
return
}
XCTFail()
}
func test_load_deliversSuccessWith200HTTPResponseWithJSONItems() async throws {
let url = URL(string: "https://aurl.com")!
let (sut, session) = makeSUT(url: url)
let validJSON = """
[
{"contact_ID" : 2 }
]
"""
let myContactURL = ContactEndPoint.myContacts.url(baseURL: url).url!
session.setResponse((validJSON.data(using: .utf8)!, responseWithStatusCode(200, url: url)), for: myContactURL)
do {
let contacts = try await sut.getMyContacts()
XCTAssertEqual(contacts.count, 1)
} catch {
XCTFail()
}
}
@MainActor func test_load_DeliverConnectivityErrorIfTaskIsCancelled() async throws {
let url = URL(string: "https://aurl.com")!
let (sut, session) = makeSUT(url: url)
let dataResponse = """
[
{"contact_ID" : 2 }
]
"""
let myContactURL = ContactEndPoint.myContacts.url(baseURL: url).url!
session.setResponse((dataResponse.data(using: .utf8)!, responseWithStatusCode(200, url: url)), for: myContactURL)
let exp = expectation(description: "Wait for load completion")
let task = Task {
do {
let contacts = try await sut.getMyContacts()
exp.fulfill()
XCTAssertEqual(contacts.count, 0)
} catch let error {
exp.fulfill()
XCTAssertEqual((error as? APIError), APIError.connectivity)
}
}
task.cancel()
await fulfillment(of: [exp])
}
No comments:
Post a Comment