Skip to content

Commit 39181ff

Browse files
committed
Add basic tests for server and api functionality
1 parent 8e12405 commit 39181ff

File tree

8 files changed

+147
-45
lines changed

8 files changed

+147
-45
lines changed

Package.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ let package = Package(
2929
.testTarget(
3030
name: "ComposableArchitecturePatternTests",
3131
dependencies: [
32-
]
32+
"ComposableArchitecturePattern",
33+
],
34+
resources: [
35+
.process("Mock/JSON")
36+
]
3337
),
3438
]
3539
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// MockCourier.swift
3+
// ComposableArchitecturePattern
4+
//
5+
// Created by Jonathan Holland on 12/8/24.
6+
//
7+
8+
import Foundation
9+
10+
public struct MockCourier: Courier {
11+
public func sendRequest(_ request: URLRequest, requestUID: UUID) async throws -> Data? {
12+
guard let url = request.url else {
13+
throw ServerAPIError.unknown(description: "Must have a valid URL to get mock data.", error: nil)
14+
}
15+
16+
return try? Data(contentsOf: url)
17+
}
18+
}

Sources/ComposableArchitecturePattern/ServerEnvironment.swift

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public enum ServerEnvironment: Hashable, CustomStringConvertible {
1717
case production(url: String)
1818
/// A test environment.
1919
case test(url: String)
20+
/// A url for testing locally, such as against local files.
21+
///
22+
/// e.g., `Bundle(for: SOMECLASS.self).url(forResource: "SomeMockFileName", withExtension: "json")`.
23+
case localTests(url: URL?)
2024

2125
public var description: String {
2226
switch self {
@@ -25,6 +29,8 @@ public enum ServerEnvironment: Hashable, CustomStringConvertible {
2529
let .production(url),
2630
let .test(url):
2731
return url
32+
case let .localTests(url):
33+
return url?.absoluteString ?? "Unknown URL"
2834
}
2935
}
3036

@@ -36,6 +42,8 @@ public enum ServerEnvironment: Hashable, CustomStringConvertible {
3642
let .production(url),
3743
let .test(url):
3844
return URL(string: url)
45+
case let .localTests(url):
46+
return url
3947
}
4048
}
4149
}
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,43 @@
1-
import SwiftSyntax
2-
import SwiftSyntaxBuilder
3-
import SwiftSyntaxMacros
4-
import SwiftSyntaxMacrosTestSupport
5-
import XCTest
6-
7-
// Macro implementations build for the host, so the corresponding module is not available when cross-compiling. Cross-compiled tests may still make use of the macro itself in end-to-end tests.
8-
#if canImport(ComposableArchitecturePatternMacros)
9-
import ComposableArchitecturePatternMacros
1+
//
2+
// ComposableArchitecturePatternTests.swift
3+
// ComposableArchitecturePattern
4+
//
5+
// Created by Jonathan Holland on 12/8/24.
6+
//
107

11-
let testMacros: [String: Macro.Type] = [
12-
"stringify": StringifyMacro.self,
13-
]
14-
#endif
8+
import XCTest
9+
@testable import ComposableArchitecturePattern
1510

1611
final class ComposableArchitecturePatternTests: XCTestCase {
17-
func testMacro() throws {
18-
#if canImport(ComposableArchitecturePatternMacros)
19-
assertMacroExpansion(
20-
"""
21-
#stringify(a + b)
22-
""",
23-
expandedSource: """
24-
(a + b, "a + b")
25-
""",
26-
macros: testMacros
27-
)
28-
#else
29-
throw XCTSkip("macros are only supported when running tests for the host platform")
30-
#endif
31-
}
32-
33-
func testMacroWithStringLiteral() throws {
34-
#if canImport(ComposableArchitecturePatternMacros)
35-
assertMacroExpansion(
36-
#"""
37-
#stringify("Hello, \(name)")
38-
"""#,
39-
expandedSource: #"""
40-
("Hello, \(name)", #""Hello, \(name)""#)
41-
"""#,
42-
macros: testMacros
43-
)
44-
#else
45-
throw XCTSkip("macros are only supported when running tests for the host platform")
46-
#endif
47-
}
12+
func testMockCourierGetsJSON() async throws {
13+
let api1 = MockServerAPI1()
14+
XCTAssertNotNil(api1.environment?.url)
15+
16+
let sut = MockCourier()
17+
let dataFromRequest1 = try await sut.sendRequest(try api1.request(.GET), requestUID: .init())
18+
XCTAssertNotNil(dataFromRequest1)
19+
20+
let api2 = MockServerAPI2()
21+
XCTAssertNotNil(api2.environment?.url)
22+
let dataFromRequest2 = try await sut.sendRequest(try api2.request(.GET), requestUID: .init())
23+
XCTAssertNotNil(dataFromRequest2)
24+
}
25+
26+
func testServerFunctionality() async throws {
27+
let sut = MockServer1()
28+
let apis = await sut.apis
29+
XCTAssertTrue(apis.count == 1)
30+
31+
let sutAPIs = await sut.apis
32+
let sutEnvironment = await sut.currentEnvironment
33+
XCTAssertTrue(sutAPIs.contains(where: { $0.environment == sutEnvironment }))
34+
}
35+
36+
func testServerAPIReturnObjectsSupport() async throws {
37+
let sut1 = MockServerAPI1()
38+
XCTAssertTrue(sut1.supports(MockResponse1.self))
39+
40+
let sut2 = MockServerAPI2()
41+
XCTAssertTrue(sut2.supports(MockResponse2.self))
42+
}
4843
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "Jonathan",
3+
"age": 30
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Steve Jobs",
3+
"age": 30,
4+
"city": "Cupertino"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// MockServerAPIs.swift
3+
// ComposableArchitecturePattern
4+
//
5+
// Created by Jonathan Holland on 12/8/24.
6+
//
7+
8+
import Foundation
9+
@testable import ComposableArchitecturePattern
10+
11+
enum MockServerEnvironments {
12+
static let mockServerAPI1 = ServerEnvironment.localTests(url: Bundle.module.url(forResource: "MockResponse1JSON", withExtension: "json"))
13+
static let mockServerAPI2 = ServerEnvironment.localTests(url: Bundle.module.url(forResource: "MockResponse2JSON", withExtension: "json"))
14+
}
15+
16+
struct MockServerAPI1: ServerAPI {
17+
let id = UUID()
18+
var environment: ServerEnvironment? = MockServerEnvironments.mockServerAPI1
19+
var path: String = ""
20+
var supportedHTTPMethods: [HTTPMethod] = [.GET]
21+
var supportedReturnObjects: [Decodable.Type]? = [MockResponse1.self]
22+
var strictEnvironmentEnforcement: Bool { false }
23+
}
24+
25+
struct MockServerAPI2: ServerAPI {
26+
let id = UUID()
27+
var environment: ServerEnvironment? = MockServerEnvironments.mockServerAPI2
28+
var path: String = ""
29+
var supportedHTTPMethods: [HTTPMethod] = [.GET]
30+
var supportedReturnObjects: [Decodable.Type]? = [MockResponse2.self]
31+
var strictEnvironmentEnforcement: Bool { false }
32+
}
33+
34+
actor MockServer1: Server {
35+
var environments: [ServerEnvironment] = [MockServerEnvironments.mockServerAPI1]
36+
lazy var currentEnvironment: ServerEnvironment? = self.environments.first
37+
lazy var courier: Courier = MockCourier()
38+
var requestsBeingProcessed = Set<UUID>()
39+
var apis: [any ServerAPI] = [MockServerAPI1()]
40+
}
41+
42+
actor MockServer2: Server {
43+
var environments: [ServerEnvironment] = [MockServerEnvironments.mockServerAPI2]
44+
lazy var currentEnvironment: ServerEnvironment? = self.environments.first
45+
lazy var courier: Courier = MockCourier()
46+
var requestsBeingProcessed = Set<UUID>()
47+
var apis: [any ServerAPI] = [MockServerAPI2()]
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// MockServerResponses.swift
3+
// ComposableArchitecturePattern
4+
//
5+
// Created by Jonathan Holland on 12/8/24.
6+
//
7+
8+
import Foundation
9+
10+
struct MockResponse1: Decodable {
11+
let name: String
12+
let age: Int
13+
}
14+
15+
struct MockResponse2: Codable {
16+
let name: String
17+
let age: Int
18+
let city: String
19+
}
20+

0 commit comments

Comments
 (0)