@@ -41,15 +41,20 @@ public class MockGraphQLServer {
41
41
}
42
42
}
43
43
44
- public var customDelay : DispatchTimeInterval ?
45
- public typealias RequestHandler < Operation : GraphQLOperation > = ( HTTPRequest < Operation > ) -> JSONObject
44
+ public typealias RequestHandler < Operation : GraphQLOperation > = ( any GraphQLRequest < Operation > ) ->
45
+ JSONObject
46
46
47
- private class RequestExpectation < Operation: GraphQLOperation > : XCTestExpectation {
47
+ private class RequestExpectation < Operation: GraphQLOperation > : XCTestExpectation , @ unchecked Sendable {
48
48
let file : StaticString
49
49
let line : UInt
50
50
let handler : RequestHandler < Operation >
51
51
52
- init ( description: String , file: StaticString = #filePath, line: UInt = #line, handler: @escaping RequestHandler < Operation > ) {
52
+ init (
53
+ description: String ,
54
+ file: StaticString = #filePath,
55
+ line: UInt = #line,
56
+ handler: @escaping RequestHandler < Operation >
57
+ ) {
53
58
self . file = file
54
59
self . line = line
55
60
self . handler = handler
@@ -60,14 +65,16 @@ public class MockGraphQLServer {
60
65
61
66
private let queue = DispatchQueue ( label: " com.apollographql.MockGraphQLServer " )
62
67
63
- public init ( ) { }
68
+ public init ( ) { }
64
69
65
70
// Since RequestExpectation is generic over a specific GraphQLOperation, we can't store these in the dictionary
66
71
// directly. Moreover, there is no way to specify the type relationship that holds between the key and value.
67
72
// To work around this, we store values as Any and use a generic subscript as a type-safe way to access them.
68
73
private var requestExpectations : [ AnyHashable : Any ] = [ : ]
69
74
70
- private subscript< Operation: GraphQLOperation > ( _ operationType: Operation . Type ) -> RequestExpectation < Operation > ? {
75
+ private subscript< Operation: GraphQLOperation > ( _ operationType: Operation . Type )
76
+ -> RequestExpectation < Operation > ?
77
+ {
71
78
get {
72
79
requestExpectations [ ObjectIdentifier ( operationType) ] as! RequestExpectation < Operation > ?
73
80
}
@@ -77,7 +84,9 @@ public class MockGraphQLServer {
77
84
}
78
85
}
79
86
80
- private subscript< Operation: GraphQLOperation > ( _ operationType: Operation ) -> RequestExpectation < Operation > ? {
87
+ private subscript< Operation: GraphQLOperation > ( _ operationType: Operation ) -> RequestExpectation <
88
+ Operation
89
+ > ? {
81
90
get {
82
91
requestExpectations [ operationType] as! RequestExpectation < Operation > ?
83
92
}
@@ -87,9 +96,19 @@ public class MockGraphQLServer {
87
96
}
88
97
}
89
98
90
- public func expect< Operation: GraphQLOperation > ( _ operationType: Operation . Type , file: StaticString = #filePath, line: UInt = #line, requestHandler: @escaping ( HTTPRequest < Operation > ) -> JSONObject ) -> XCTestExpectation {
99
+ public func expect< Operation: GraphQLOperation > (
100
+ _ operationType: Operation . Type ,
101
+ file: StaticString = #filePath,
102
+ line: UInt = #line,
103
+ requestHandler: @escaping RequestHandler < Operation >
104
+ ) -> XCTestExpectation {
91
105
return queue. sync {
92
- let expectation = RequestExpectation < Operation > ( description: " Served request for \( String ( describing: operationType) ) " , file: file, line: line, handler: requestHandler)
106
+ let expectation = RequestExpectation < Operation > (
107
+ description: " Served request for \( String ( describing: operationType) ) " ,
108
+ file: file,
109
+ line: line,
110
+ handler: requestHandler
111
+ )
93
112
expectation. assertForOverFulfill = true
94
113
95
114
self [ operationType] = expectation
@@ -98,9 +117,19 @@ public class MockGraphQLServer {
98
117
}
99
118
}
100
119
101
- public func expect< Operation: GraphQLOperation > ( _ operation: Operation , file: StaticString = #filePath, line: UInt = #line, requestHandler: @escaping ( HTTPRequest < Operation > ) -> JSONObject ) -> XCTestExpectation {
120
+ public func expect< Operation: GraphQLOperation > (
121
+ _ operation: Operation ,
122
+ file: StaticString = #filePath,
123
+ line: UInt = #line,
124
+ requestHandler: @escaping RequestHandler < Operation >
125
+ ) -> XCTestExpectation {
102
126
return queue. sync {
103
- let expectation = RequestExpectation < Operation > ( description: " Served request for \( String ( describing: operation. self) ) " , file: file, line: line, handler: requestHandler)
127
+ let expectation = RequestExpectation < Operation > (
128
+ description: " Served request for \( String ( describing: operation. self) ) " ,
129
+ file: file,
130
+ line: line,
131
+ handler: requestHandler
132
+ )
104
133
expectation. assertForOverFulfill = true
105
134
106
135
self [ operation] = expectation
@@ -109,21 +138,76 @@ public class MockGraphQLServer {
109
138
}
110
139
}
111
140
112
- func serve< Operation> ( request: HTTPRequest < Operation > , completionHandler: @escaping ( Result < JSONObject , any Error > ) -> Void ) where Operation: GraphQLOperation {
141
+ func serve< Operation> (
142
+ request: any GraphQLRequest < Operation >
143
+ ) async throws -> JSONObject where Operation: GraphQLOperation {
113
144
let operationType = type ( of: request. operation)
114
145
115
146
if let expectation = self [ request. operation] ?? self [ operationType] {
116
147
// Dispatch after a small random delay to spread out concurrent requests and simulate somewhat real-world conditions.
117
- queue. asyncAfter ( deadline: . now( ) + ( customDelay ?? . milliseconds( Int . random ( in: 10 ... 50 ) ) ) ) {
118
- completionHandler ( . success( expectation. handler ( request) ) )
119
- expectation. fulfill ( )
120
- }
148
+ try await Task . sleep ( nanoseconds: UInt64 . random ( in: 10 ... 50 ) * 1_000_000 )
149
+ expectation. fulfill ( )
150
+ return expectation. handler ( request)
121
151
122
152
} else {
123
- queue. async {
124
- completionHandler ( . failure( ServerError . unexpectedRequest ( String ( describing: operationType) ) ) )
125
- }
153
+ throw ServerError . unexpectedRequest ( String ( describing: operationType) )
154
+ }
155
+ }
156
+ }
157
+
158
+ public struct MockGraphQLServerSession : ApolloURLSession {
159
+
160
+ nonisolated ( unsafe) let server: MockGraphQLServer
161
+
162
+ init ( server: MockGraphQLServer ) {
163
+ self . server = server
164
+ }
165
+
166
+ public func chunks( for request: some GraphQLRequest ) async throws -> ( any AsyncChunkSequence , URLResponse ) {
167
+ let ( stream, continuation) = MockAsyncChunkSequence . makeStream ( )
168
+ do {
169
+ let body = try await server. serve ( request: request)
170
+ let data = try JSONSerializationFormat . serialize ( value: body)
171
+ continuation. yield ( data)
172
+ continuation. finish ( )
173
+
174
+ } catch {
175
+ continuation. finish ( throwing: error)
126
176
}
127
177
178
+ let httpResponse = HTTPURLResponse (
179
+ url: TestURL . mockServer. url,
180
+ statusCode: 200 ,
181
+ httpVersion: nil ,
182
+ headerFields: nil
183
+ ) !
184
+ return ( stream, httpResponse)
185
+ }
186
+
187
+ public func invalidateAndCancel( ) {
188
+ }
189
+
190
+ }
191
+
192
+
193
+ public struct MockAsyncChunkSequence : AsyncChunkSequence {
194
+ public typealias UnderlyingStream = AsyncThrowingStream < Data , any Error >
195
+
196
+ public typealias AsyncIterator = UnderlyingStream . AsyncIterator
197
+
198
+ public typealias Element = Data
199
+
200
+ let underlying : UnderlyingStream
201
+
202
+ public func makeAsyncIterator( ) -> UnderlyingStream . AsyncIterator {
203
+ underlying. makeAsyncIterator ( )
204
+ }
205
+
206
+ public static func makeStream( ) -> (
207
+ stream: MockAsyncChunkSequence ,
208
+ continuation: UnderlyingStream . Continuation
209
+ ) {
210
+ let ( s, c) = UnderlyingStream . makeStream ( of: Data . self)
211
+ return ( Self . init ( underlying: s) , c)
128
212
}
129
213
}
0 commit comments