Skip to content

Commit d008381

Browse files
authored
Provide the codegen with an option to generate test clients (#870)
* Provide the codegen with an option to generate test clients Motivation: When consuming gRPC it is often helpful to be able to write tests that ensure the client is integrated correctly. At the moment this is only possible by running a local gRPC server with a custom service handler to return the responses you would like to test. Modifications: This builds on work in #855, #864, and #865. This pull request introduces code generation for the test clients. It provides type-safe wrappers and convenience methods on top of `FakeChannel` and a code-gen option to enable 'TestClient' generation. It also removes an `init` requirement on the `GRPCClient` protocol. Result: Users can generate test clients. * Regenerate * fix type, add assertion * Add "Remaining"
1 parent 2059a6e commit d008381

File tree

22 files changed

+1178
-347
lines changed

22 files changed

+1178
-347
lines changed

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ ECHO_PROTO=Sources/Examples/Echo/Model/echo.proto
7474
ECHO_PB=$(ECHO_PROTO:.proto=.pb.swift)
7575
ECHO_GRPC=$(ECHO_PROTO:.proto=.grpc.swift)
7676

77+
# For Echo we'll generate the test client as well.
78+
${ECHO_GRPC}: ${ECHO_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
79+
protoc $< \
80+
--proto_path=$(dir $<) \
81+
--plugin=${PROTOC_GEN_GRPC_SWIFT} \
82+
--grpc-swift_opt=Visibility=Public,TestClient=true \
83+
--grpc-swift_out=$(dir $<)
84+
7785
# Generates protobufs and gRPC client and server for the Echo example
7886
.PHONY:
7987
generate-echo: ${ECHO_PB} ${ECHO_GRPC}

Sources/Examples/Echo/Model/echo.grpc.swift

Lines changed: 142 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -31,65 +31,27 @@ import SwiftProtobuf
3131
public protocol Echo_EchoClientProtocol: GRPCClient {
3232
func get(
3333
_ request: Echo_EchoRequest,
34-
callOptions: CallOptions
34+
callOptions: CallOptions?
3535
) -> UnaryCall<Echo_EchoRequest, Echo_EchoResponse>
3636

3737
func expand(
3838
_ request: Echo_EchoRequest,
39-
callOptions: CallOptions,
39+
callOptions: CallOptions?,
4040
handler: @escaping (Echo_EchoResponse) -> Void
4141
) -> ServerStreamingCall<Echo_EchoRequest, Echo_EchoResponse>
4242

4343
func collect(
44-
callOptions: CallOptions
44+
callOptions: CallOptions?
4545
) -> ClientStreamingCall<Echo_EchoRequest, Echo_EchoResponse>
4646

4747
func update(
48-
callOptions: CallOptions,
48+
callOptions: CallOptions?,
4949
handler: @escaping (Echo_EchoResponse) -> Void
5050
) -> BidirectionalStreamingCall<Echo_EchoRequest, Echo_EchoResponse>
5151

5252
}
5353

5454
extension Echo_EchoClientProtocol {
55-
public func get(
56-
_ request: Echo_EchoRequest
57-
) -> UnaryCall<Echo_EchoRequest, Echo_EchoResponse> {
58-
return self.get(request, callOptions: self.defaultCallOptions)
59-
}
60-
61-
public func expand(
62-
_ request: Echo_EchoRequest,
63-
handler: @escaping (Echo_EchoResponse) -> Void
64-
) -> ServerStreamingCall<Echo_EchoRequest, Echo_EchoResponse> {
65-
return self.expand(request, callOptions: self.defaultCallOptions, handler: handler)
66-
}
67-
68-
public func collect() -> ClientStreamingCall<Echo_EchoRequest, Echo_EchoResponse> {
69-
return self.collect(callOptions: self.defaultCallOptions)
70-
}
71-
72-
public func update(
73-
handler: @escaping (Echo_EchoResponse) -> Void
74-
) -> BidirectionalStreamingCall<Echo_EchoRequest, Echo_EchoResponse> {
75-
return self.update(callOptions: self.defaultCallOptions, handler: handler)
76-
}
77-
78-
}
79-
80-
public final class Echo_EchoClient: Echo_EchoClientProtocol {
81-
public let channel: GRPCChannel
82-
public var defaultCallOptions: CallOptions
83-
84-
/// Creates a client for the echo.Echo service.
85-
///
86-
/// - Parameters:
87-
/// - channel: `GRPCChannel` to the service host.
88-
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
89-
public init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) {
90-
self.channel = channel
91-
self.defaultCallOptions = defaultCallOptions
92-
}
9355

9456
/// Immediately returns an echo of a request.
9557
///
@@ -99,12 +61,12 @@ public final class Echo_EchoClient: Echo_EchoClientProtocol {
9961
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
10062
public func get(
10163
_ request: Echo_EchoRequest,
102-
callOptions: CallOptions
64+
callOptions: CallOptions? = nil
10365
) -> UnaryCall<Echo_EchoRequest, Echo_EchoResponse> {
10466
return self.makeUnaryCall(
10567
path: "/echo.Echo/Get",
10668
request: request,
107-
callOptions: callOptions
69+
callOptions: callOptions ?? self.defaultCallOptions
10870
)
10971
}
11072

@@ -117,13 +79,13 @@ public final class Echo_EchoClient: Echo_EchoClientProtocol {
11779
/// - Returns: A `ServerStreamingCall` with futures for the metadata and status.
11880
public func expand(
11981
_ request: Echo_EchoRequest,
120-
callOptions: CallOptions,
82+
callOptions: CallOptions? = nil,
12183
handler: @escaping (Echo_EchoResponse) -> Void
12284
) -> ServerStreamingCall<Echo_EchoRequest, Echo_EchoResponse> {
12385
return self.makeServerStreamingCall(
12486
path: "/echo.Echo/Expand",
12587
request: request,
126-
callOptions: callOptions,
88+
callOptions: callOptions ?? self.defaultCallOptions,
12789
handler: handler
12890
)
12991
}
@@ -137,11 +99,11 @@ public final class Echo_EchoClient: Echo_EchoClientProtocol {
13799
/// - callOptions: Call options.
138100
/// - Returns: A `ClientStreamingCall` with futures for the metadata, status and response.
139101
public func collect(
140-
callOptions: CallOptions
102+
callOptions: CallOptions? = nil
141103
) -> ClientStreamingCall<Echo_EchoRequest, Echo_EchoResponse> {
142104
return self.makeClientStreamingCall(
143105
path: "/echo.Echo/Collect",
144-
callOptions: callOptions
106+
callOptions: callOptions ?? self.defaultCallOptions
145107
)
146108
}
147109

@@ -155,17 +117,147 @@ public final class Echo_EchoClient: Echo_EchoClientProtocol {
155117
/// - handler: A closure called when each response is received from the server.
156118
/// - Returns: A `ClientStreamingCall` with futures for the metadata and status.
157119
public func update(
158-
callOptions: CallOptions,
120+
callOptions: CallOptions? = nil,
159121
handler: @escaping (Echo_EchoResponse) -> Void
160122
) -> BidirectionalStreamingCall<Echo_EchoRequest, Echo_EchoResponse> {
161123
return self.makeBidirectionalStreamingCall(
162124
path: "/echo.Echo/Update",
163-
callOptions: callOptions,
125+
callOptions: callOptions ?? self.defaultCallOptions,
164126
handler: handler
165127
)
166128
}
167129
}
168130

131+
public final class Echo_EchoClient: Echo_EchoClientProtocol {
132+
public let channel: GRPCChannel
133+
public var defaultCallOptions: CallOptions
134+
135+
/// Creates a client for the echo.Echo service.
136+
///
137+
/// - Parameters:
138+
/// - channel: `GRPCChannel` to the service host.
139+
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
140+
public init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) {
141+
self.channel = channel
142+
self.defaultCallOptions = defaultCallOptions
143+
}
144+
}
145+
146+
public final class Echo_EchoTestClient: Echo_EchoClientProtocol {
147+
private let fakeChannel: FakeChannel
148+
public var defaultCallOptions: CallOptions
149+
150+
public var channel: GRPCChannel {
151+
return self.fakeChannel
152+
}
153+
154+
public init(
155+
fakeChannel: FakeChannel = FakeChannel(),
156+
defaultCallOptions callOptions: CallOptions = CallOptions()
157+
) {
158+
self.fakeChannel = fakeChannel
159+
self.defaultCallOptions = callOptions
160+
}
161+
162+
/// Make a unary response for the Get RPC. This must be called
163+
/// before calling 'get'. See also 'FakeUnaryResponse'.
164+
///
165+
/// - Parameter requestHandler: a handler for request parts sent by the RPC.
166+
public func makeGetResponseStream(
167+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
168+
) -> FakeUnaryResponse<Echo_EchoRequest, Echo_EchoResponse> {
169+
return self.fakeChannel.makeFakeUnaryResponse(path: "/echo.Echo/Get", requestHandler: requestHandler)
170+
}
171+
172+
public func enqueueGetResponse(
173+
_ response: Echo_EchoResponse,
174+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
175+
) {
176+
let stream = self.makeGetResponseStream(requestHandler)
177+
// This is the only operation on the stream; try! is fine.
178+
try! stream.sendMessage(response)
179+
}
180+
181+
/// Returns true if there are response streams enqueued for 'Get'
182+
public var hasGetResponsesRemaining: Bool {
183+
return self.fakeChannel.hasFakeResponseEnqueued(forPath: "/echo.Echo/Get")
184+
}
185+
186+
/// Make a streaming response for the Expand RPC. This must be called
187+
/// before calling 'expand'. See also 'FakeStreamingResponse'.
188+
///
189+
/// - Parameter requestHandler: a handler for request parts sent by the RPC.
190+
public func makeExpandResponseStream(
191+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
192+
) -> FakeStreamingResponse<Echo_EchoRequest, Echo_EchoResponse> {
193+
return self.fakeChannel.makeFakeStreamingResponse(path: "/echo.Echo/Expand", requestHandler: requestHandler)
194+
}
195+
196+
public func enqueueExpandResponses(
197+
_ responses: [Echo_EchoResponse],
198+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
199+
) {
200+
let stream = self.makeExpandResponseStream(requestHandler)
201+
// These are the only operation on the stream; try! is fine.
202+
responses.forEach { try! stream.sendMessage($0) }
203+
try! stream.sendEnd()
204+
}
205+
206+
/// Returns true if there are response streams enqueued for 'Expand'
207+
public var hasExpandResponsesRemaining: Bool {
208+
return self.fakeChannel.hasFakeResponseEnqueued(forPath: "/echo.Echo/Expand")
209+
}
210+
211+
/// Make a unary response for the Collect RPC. This must be called
212+
/// before calling 'collect'. See also 'FakeUnaryResponse'.
213+
///
214+
/// - Parameter requestHandler: a handler for request parts sent by the RPC.
215+
public func makeCollectResponseStream(
216+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
217+
) -> FakeUnaryResponse<Echo_EchoRequest, Echo_EchoResponse> {
218+
return self.fakeChannel.makeFakeUnaryResponse(path: "/echo.Echo/Collect", requestHandler: requestHandler)
219+
}
220+
221+
public func enqueueCollectResponse(
222+
_ response: Echo_EchoResponse,
223+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
224+
) {
225+
let stream = self.makeCollectResponseStream(requestHandler)
226+
// This is the only operation on the stream; try! is fine.
227+
try! stream.sendMessage(response)
228+
}
229+
230+
/// Returns true if there are response streams enqueued for 'Collect'
231+
public var hasCollectResponsesRemaining: Bool {
232+
return self.fakeChannel.hasFakeResponseEnqueued(forPath: "/echo.Echo/Collect")
233+
}
234+
235+
/// Make a streaming response for the Update RPC. This must be called
236+
/// before calling 'update'. See also 'FakeStreamingResponse'.
237+
///
238+
/// - Parameter requestHandler: a handler for request parts sent by the RPC.
239+
public func makeUpdateResponseStream(
240+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
241+
) -> FakeStreamingResponse<Echo_EchoRequest, Echo_EchoResponse> {
242+
return self.fakeChannel.makeFakeStreamingResponse(path: "/echo.Echo/Update", requestHandler: requestHandler)
243+
}
244+
245+
public func enqueueUpdateResponses(
246+
_ responses: [Echo_EchoResponse],
247+
_ requestHandler: @escaping (FakeRequestPart<Echo_EchoRequest>) -> () = { _ in }
248+
) {
249+
let stream = self.makeUpdateResponseStream(requestHandler)
250+
// These are the only operation on the stream; try! is fine.
251+
responses.forEach { try! stream.sendMessage($0) }
252+
try! stream.sendEnd()
253+
}
254+
255+
/// Returns true if there are response streams enqueued for 'Update'
256+
public var hasUpdateResponsesRemaining: Bool {
257+
return self.fakeChannel.hasFakeResponseEnqueued(forPath: "/echo.Echo/Update")
258+
}
259+
}
260+
169261
/// To build a server, implement a class that conforms to this protocol.
170262
public protocol Echo_EchoProvider: CallHandlerProvider {
171263
/// Immediately returns an echo of a request.

Sources/Examples/HelloWorld/Model/helloworld.grpc.swift

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,29 @@ import SwiftProtobuf
3131
public protocol Helloworld_GreeterClientProtocol: GRPCClient {
3232
func sayHello(
3333
_ request: Helloworld_HelloRequest,
34-
callOptions: CallOptions
34+
callOptions: CallOptions?
3535
) -> UnaryCall<Helloworld_HelloRequest, Helloworld_HelloReply>
3636

3737
}
3838

3939
extension Helloworld_GreeterClientProtocol {
40+
41+
/// Sends a greeting.
42+
///
43+
/// - Parameters:
44+
/// - request: Request to send to SayHello.
45+
/// - callOptions: Call options.
46+
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
4047
public func sayHello(
41-
_ request: Helloworld_HelloRequest
48+
_ request: Helloworld_HelloRequest,
49+
callOptions: CallOptions? = nil
4250
) -> UnaryCall<Helloworld_HelloRequest, Helloworld_HelloReply> {
43-
return self.sayHello(request, callOptions: self.defaultCallOptions)
51+
return self.makeUnaryCall(
52+
path: "/helloworld.Greeter/SayHello",
53+
request: request,
54+
callOptions: callOptions ?? self.defaultCallOptions
55+
)
4456
}
45-
4657
}
4758

4859
public final class Helloworld_GreeterClient: Helloworld_GreeterClientProtocol {
@@ -58,23 +69,6 @@ public final class Helloworld_GreeterClient: Helloworld_GreeterClientProtocol {
5869
self.channel = channel
5970
self.defaultCallOptions = defaultCallOptions
6071
}
61-
62-
/// Sends a greeting.
63-
///
64-
/// - Parameters:
65-
/// - request: Request to send to SayHello.
66-
/// - callOptions: Call options.
67-
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
68-
public func sayHello(
69-
_ request: Helloworld_HelloRequest,
70-
callOptions: CallOptions
71-
) -> UnaryCall<Helloworld_HelloRequest, Helloworld_HelloReply> {
72-
return self.makeUnaryCall(
73-
path: "/helloworld.Greeter/SayHello",
74-
request: request,
75-
callOptions: callOptions
76-
)
77-
}
7872
}
7973

8074
/// To build a server, implement a class that conforms to this protocol.

0 commit comments

Comments
 (0)