You can test all the Endpoints and models which contain business logic.
In the case of Endpoint, test how it creates the URLRequest object (method makeRequest
):
- HTTP method
- URL address
- HTTP Body
- HTTP headers
Example
There is a BookListEndpoint
in the example project. This endpoint is used to obtain a list of books. The following example shows how to test this Endpoint.
import ExampleAPI
import XCTest
final class BookListEndpointTests: XCTestCase {
func testMakeRequest() throws {
let endpoint = BookListEndpoint()
let urlRequest = try endpoint.makeRequest()
XCTAssertEqual(urlRequest.httpMethod, "GET")
XCTAssertNil(urlRequest.httpBody)
XCTAssertEqual(urlRequest.url?.absoluteString, "books")
}
}
This test checks that:
- HTTP method equals to "GET"
- HTTP body doesn't exist
- URL equals to "books"
If a model object contains business logic, then this object must be tested. For example, if a model object has computed properties where data is formatted.
You can also test the decoding of a model object in the case of complex transformations, for example, converting a string to a date.
/// An abstract access code that has an expiration date
struct Code: Decodable, Equatable {
/// Code value, e.g. "1234"
let code: String
/// Code expiration date
let endDate: Date
}
final class CodeTests: XCTestCase {
func testDecode() throws {
let json = """
{
"code": "1234",
"end_date": "2019-03-21T13:13:36Z"
}
""".data(using: .utf8)!
let code = try JSONDecoder().decode(Code.self, from: json)
XCTAssertEqual(
code.endDate,
makeDate(year: 2019, month: 3, day: 21, hour: 13, minute: 13, second: 36))
}
private func makeDate(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> Date {
return DateComponents(
calendar: .current,
timeZone: TimeZone(secondsFromGMT: 0),
year: year, month: month, day: day,
hour: hour, minute: minute, second: second).date!
}
}
The following helpers can be used to improve readability and reduce the amount of code in tests:
Asserts.swift
func assertGET(_ urlRequest: URLRequest, file: StaticString = #file, line: UInt = #line) {
guard let method = urlRequest.httpMethod else {
return XCTFail("The request does not contains HTTP method", file: file, line: line)
}
XCTAssertEqual(method, "GET", file: file, line: line)
XCTAssertNil(urlRequest.httpBody, "GET request must not contains body", file: file, line: line)
}
func assertPOST(_ urlRequest: URLRequest, file: StaticString = #file, line: UInt = #line) {
guard let method = urlRequest.httpMethod else {
return XCTFail("The request does not contains HTTP method", file: file, line: line)
}
XCTAssertEqual(method, "POST", file: file, line: line)
}
func assertDELETE(_ urlRequest: URLRequest, file: StaticString = #file, line: UInt = #line) {
guard let method = urlRequest.httpMethod else {
return XCTFail("The request does not contains HTTP method", file: file, line: line)
}
XCTAssertEqual(method, "DELETE", file: file, line: line)
}
func assertPATCH(_ urlRequest: URLRequest, file: StaticString = #file, line: UInt = #line) {
guard let method = urlRequest.httpMethod else {
return XCTFail("The request does not contains HTTP method", file: file, line: line)
}
XCTAssertEqual(method, "PATCH", file: file, line: line)
}
func assertPath(_ urlRequest: URLRequest, _ path: String, file: StaticString = #file, line: UInt = #line) {
guard let url = urlRequest.url else {
return XCTFail("The request does not contains HTTP method", file: file, line: line)
}
XCTAssertEqual(url.path, path, "Paths does not equal", file: file, line: line)
}
func assertURL(_ urlRequest: URLRequest, _ urlString: String, file: StaticString = #file, line: UInt = #line) {
guard let url = urlRequest.url else {
return XCTFail("The request does not contains HTTP method", file: file, line: line)
}
XCTAssertEqual(url.absoluteString, urlString, "URLs does not equal", file: file, line: line)
}
The example above could be written like this:
func testMakeRequest() throws {
let endpoint = BookListEndpoint()
let urlRequest = try endpoint.makeRequest()
assertGET(urlRequest)
assertURL(urlRequest, "books")
}