Skip to content

Commit f116d8b

Browse files
committed
Add example project using feature/swift-concurrency PactSwift branch
1 parent 0f6f411 commit f116d8b

File tree

13 files changed

+323
-0
lines changed

13 files changed

+323
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright © 2025 Marko Justinek. All rights reserved.
2+
3+
enum APIError: Error {
4+
case invalidURL
5+
case clientError(code: Int, message: String)
6+
case serverError(code: Int, message: String)
7+
case unknownError(code: Int, message: String)
8+
case decodingError(String?)
9+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright © 2025 Marko Justinek. All rights reserved.
2+
3+
import Foundation
4+
5+
actor APIService {
6+
7+
func fetch<T: Decodable>(from url: URL, decoding: T.Type) async throws -> T {
8+
let (data, response) = try await URLSession.shared.data(from: url)
9+
10+
guard let httpResponse = response as? HTTPURLResponse else {
11+
throw APIError.unknownError(code: -1, message: "Invalid response format")
12+
}
13+
14+
switch httpResponse.statusCode {
15+
case 200...299:
16+
do {
17+
return try JSONDecoder().decode(T.self, from: data)
18+
} catch {
19+
throw APIError.decodingError(error.localizedDescription)
20+
}
21+
22+
case 400...499:
23+
throw APIError.clientError(code: httpResponse.statusCode, message: "Client error occurred")
24+
25+
case 500...599:
26+
throw APIError.serverError(code: httpResponse.statusCode, message: "Server error occurred")
27+
28+
default:
29+
throw APIError.unknownError(code: httpResponse.statusCode, message: "Unexpected error occurred")
30+
}
31+
}
32+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"platform" : "ios",
6+
"size" : "1024x1024"
7+
},
8+
{
9+
"appearances" : [
10+
{
11+
"appearance" : "luminosity",
12+
"value" : "dark"
13+
}
14+
],
15+
"idiom" : "universal",
16+
"platform" : "ios",
17+
"size" : "1024x1024"
18+
},
19+
{
20+
"appearances" : [
21+
{
22+
"appearance" : "luminosity",
23+
"value" : "tinted"
24+
}
25+
],
26+
"idiom" : "universal",
27+
"platform" : "ios",
28+
"size" : "1024x1024"
29+
},
30+
{
31+
"idiom" : "mac",
32+
"scale" : "1x",
33+
"size" : "16x16"
34+
},
35+
{
36+
"idiom" : "mac",
37+
"scale" : "2x",
38+
"size" : "16x16"
39+
},
40+
{
41+
"idiom" : "mac",
42+
"scale" : "1x",
43+
"size" : "32x32"
44+
},
45+
{
46+
"idiom" : "mac",
47+
"scale" : "2x",
48+
"size" : "32x32"
49+
},
50+
{
51+
"idiom" : "mac",
52+
"scale" : "1x",
53+
"size" : "128x128"
54+
},
55+
{
56+
"idiom" : "mac",
57+
"scale" : "2x",
58+
"size" : "128x128"
59+
},
60+
{
61+
"idiom" : "mac",
62+
"scale" : "1x",
63+
"size" : "256x256"
64+
},
65+
{
66+
"idiom" : "mac",
67+
"scale" : "2x",
68+
"size" : "256x256"
69+
},
70+
{
71+
"idiom" : "mac",
72+
"scale" : "1x",
73+
"size" : "512x512"
74+
},
75+
{
76+
"idiom" : "mac",
77+
"scale" : "2x",
78+
"size" : "512x512"
79+
}
80+
],
81+
"info" : {
82+
"author" : "xcode",
83+
"version" : 1
84+
}
85+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright © 2025 Marko Justinek. All rights reserved.
2+
3+
import SwiftUI
4+
5+
struct ContentView: View {
6+
var body: some View {
7+
VStack {
8+
Image(systemName: "globe")
9+
.imageScale(.large)
10+
.foregroundStyle(.tint)
11+
Text("Hello, world!")
12+
}
13+
.padding()
14+
}
15+
}
16+
17+
#Preview {
18+
ContentView()
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.app-sandbox</key>
6+
<true/>
7+
<key>com.apple.security.files.user-selected.read-only</key>
8+
<true/>
9+
</dict>
10+
</plist>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright © 2025 Marko Justinek. All rights reserved.
2+
3+
import SwiftUI
4+
5+
@main
6+
struct Pact_ConcurrencyApp: App {
7+
var body: some Scene {
8+
WindowGroup {
9+
ContentView()
10+
}
11+
}
12+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright © 2025 Marko Justinek. All rights reserved.
2+
3+
import PactSwift
4+
import XCTest
5+
6+
@testable import Pact_Concurrency
7+
8+
final class Pact_ConcurrencyTests: XCTestCase {
9+
10+
var builder: PactBuilder!
11+
12+
@MainActor
13+
class override func setUp() {
14+
super.setUp()
15+
try! Logging.initialize(
16+
[
17+
Logging.Sink.Config(.standardError, filter: .trace),
18+
]
19+
)
20+
}
21+
22+
override func setUpWithError() throws {
23+
try super.setUpWithError()
24+
25+
guard builder == nil else {
26+
return
27+
}
28+
29+
let pact = try Pact(consumer: "Consumer - App", provider: "API Provider")
30+
.withSpecification(.v4)
31+
32+
let config = PactBuilder.Config(pactDirectory: Utils.getProjectRoot().appending("/tmp"))
33+
builder = PactBuilder(pact: pact, config: config)
34+
}
35+
36+
// MARK: - Tests
37+
38+
func testGET() async throws {
39+
try builder
40+
.uponReceiving("A GET request")
41+
.given(
42+
Interaction.ProviderState(
43+
description: "String",
44+
name: #file,
45+
value: #function
46+
)
47+
)
48+
.withRequest(method: .GET, path: "/hello")
49+
.willRespond(with: 200) { response in
50+
try response.jsonBody(
51+
.like(["hello":"World!"])
52+
)
53+
}
54+
55+
try await builder
56+
.verify { context in
57+
let apiService = APIService()
58+
let foo = try await apiService.fetch(from: URL(string: "\(context.mockServerURL)/hello")!, decoding: Foo.self)
59+
60+
XCTAssertEqual(Foo(hello: "World!"), foo)
61+
}
62+
}
63+
}
64+
65+
struct Foo: Decodable, Equatable {
66+
let hello: String
67+
}

0 commit comments

Comments
 (0)