Implemented the public LocationsService service.

This commit is contained in:
Javier Cicchelli 2023-04-10 17:08:44 +02:00
parent c3e0d86870
commit 2508275714
6 changed files with 212 additions and 16 deletions

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
/// This class overrides the `URLProtocol` protocol used by the `URLSession` to handle the loading of protocol-specific URL data so it is possible to mock URL response for testing purposes.
public class MockURLProtocol: URLProtocol { public class MockURLProtocol: URLProtocol {
// MARK: Properties // MARK: Properties

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
/// This model includes the data to be injected into an specific URL at the time of mocking its response.
public struct MockURLResponse { public struct MockURLResponse {
// MARK: Properties // MARK: Properties

View File

@ -0,0 +1,39 @@
//
// LocationsService.swift
// Locations
//
// Created by Javier Cicchelli on 10/04/2023.
// Copyright © 2023 Röck+Cöde. All rights reserved.
//
import APICore
import Foundation
public struct LocationsService {
// MARK: Properties
private let client: Client
// MARK: Initialisers
public init(configuration: URLSessionConfiguration = .default) {
self.client = LocationsClient(configuration: configuration)
}
// MARK: Functions
public func getLocations() async throws -> [Location] {
try await client.request(
endpoint: GetLocationsEndpoint(),
for: Locations.self
).locations
}
}
// MARK: - Models
struct Locations: Decodable, Equatable {
public let locations: [Location]
}

View File

@ -45,7 +45,7 @@ final class LocationsClientTests: XCTestCase {
let endpoint = GetLocationsEndpoint() let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url url = try makeURLRequest(endpoint: endpoint).url
data = .locations data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse( MockURLProtocol.mockData[url] = MockURLResponse(
status: 200, status: 200,
@ -85,7 +85,7 @@ final class LocationsClientTests: XCTestCase {
let endpoint = GetLocationsEndpoint() let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url url = try makeURLRequest(endpoint: endpoint).url
data = .locations data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse( MockURLProtocol.mockData[url] = MockURLResponse(
status: 404, status: 404,
@ -108,7 +108,7 @@ final class LocationsClientTests: XCTestCase {
let endpoint = GetLocationsEndpoint() let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url url = try makeURLRequest(endpoint: endpoint).url
data = .locations data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse( MockURLProtocol.mockData[url] = MockURLResponse(
status: 500, status: 500,
@ -131,7 +131,7 @@ final class LocationsClientTests: XCTestCase {
let endpoint = GetLocationsEndpoint() let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url url = try makeURLRequest(endpoint: endpoint).url
data = .locations data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse( MockURLProtocol.mockData[url] = MockURLResponse(
status: 302, status: 302,
@ -150,15 +150,3 @@ final class LocationsClientTests: XCTestCase {
} }
} }
// MARK: - Models
private struct Locations: Decodable, Equatable {
public let locations: [Location]
}
// MARK: - String+Constants
private extension Data {
static let locations = "{\"locations\":[{\"name\":\"Amsterdam\",\"lat\":52.3547498,\"long\":4.8339215},{\"name\":\"Mumbai\",\"lat\":19.0823998,\"long\":72.8111468},{\"name\":\"Copenhagen\",\"lat\":55.6713442,\"long\":12.523785},{\"lat\":40.4380638,\"long\":-3.7495758}]}".data(using: .utf8)
}

View File

@ -0,0 +1,15 @@
//
// Data+Constants.swift
// LocationsTests
//
// Created by Javier Cicchelli on 10/04/2023.
// Copyright © 2023 Röck+Cöde. All rights reserved.
//
import Foundation
extension Data {
enum Responses {
static let locations = "{\"locations\":[{\"name\":\"Amsterdam\",\"lat\":52.3547498,\"long\":4.8339215},{\"name\":\"Mumbai\",\"lat\":19.0823998,\"long\":72.8111468},{\"name\":\"Copenhagen\",\"lat\":55.6713442,\"long\":12.523785},{\"lat\":40.4380638,\"long\":-3.7495758}]}".data(using: .utf8)
}
}

View File

@ -0,0 +1,152 @@
//
// LocationsServiceTests.swift
// LocationsTests
//
// Created by Javier Cicchelli on 10/04/2023.
// Copyright © 2023 Röck+Cöde. All rights reserved.
//
import APICore
import XCTest
@testable import Locations
final class LocationsServiceTests: XCTestCase {
// MARK: Properties
private let makeURLRequest = MakeURLRequestUseCase()
private let sessionConfiguration = {
let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [MockURLProtocol.self]
return configuration
}()
private var service: LocationsService!
private var url: URL!
private var data: Data!
// MARK: Setup
override func setUp() async throws {
service = .init(configuration: sessionConfiguration)
}
override func tearDown() async throws {
service = nil
}
// MARK: Tests
func test_getLocations_whenResponseOK() async throws {
// GIVEN
let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url
data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse(
status: 200,
headers: [:],
data: data
)
// WHEN
let result = try await service.getLocations()
// THEN
XCTAssertEqual(result, [
.init(
name: "Amsterdam",
latitude: 52.3547498,
longitude: 4.8339215
),
.init(
name: "Mumbai",
latitude: 19.0823998,
longitude: 72.8111468
),
.init(
name: "Copenhagen",
latitude: 55.6713442,
longitude: 12.523785
),
.init(
latitude: 40.4380638,
longitude: -3.7495758
)
])
}
func test_getLocations_whenResponseClientError() async throws {
// GIVEN
let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url
data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse(
status: 404,
headers: [:],
data: data
)
// WHEN & THEN
do {
_ = try await service.getLocations()
} catch LocationsClientError.statusErrorClient {
XCTAssertTrue(true)
} catch {
XCTAssertTrue(false)
}
}
func test_getLocations_whenResponseServerError() async throws {
// GIVEN
let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url
data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse(
status: 500,
headers: [:],
data: data
)
// WHEN & THEN
do {
_ = try await service.getLocations()
} catch LocationsClientError.statusErrorServer {
XCTAssertTrue(true)
} catch {
XCTAssertTrue(false)
}
}
func test_getLocations_whenResponseUnexpectedError() async throws {
// GIVEN
let endpoint = GetLocationsEndpoint()
url = try makeURLRequest(endpoint: endpoint).url
data = .Responses.locations
MockURLProtocol.mockData[url] = MockURLResponse(
status: 302,
headers: [:],
data: data
)
// WHEN & THEN
do {
_ = try await service.getLocations()
} catch LocationsClientError.statusErrorUnexpected {
XCTAssertTrue(true)
} catch {
XCTAssertTrue(false)
}
}
}