• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# How to write unit tests for gRPC C client.
2
3tl;dr: [Example code](https://github.com/grpc/grpc/blob/master/test/cpp/end2end/mock_test.cc).
4
5To unit-test client-side logic via the synchronous API, gRPC provides a mocked Stub based on googletest(googlemock) that can be programmed upon and easily incorporated in the test code.
6
7For instance, consider an EchoService like this:
8
9
10```proto
11service EchoTestService {
12        rpc Echo(EchoRequest) returns (EchoResponse);
13        rpc BidiStream(stream EchoRequest) returns (stream EchoResponse);
14}
15```
16
17The code generated would look something like this:
18
19```c
20class EchoTestService final {
21  public:
22  class StubInterface {
23    virtual ::grpc::Status Echo(::grpc::ClientContext* context, const ::grpc::testing::EchoRequest& request, ::grpc::testing::EchoResponse* response) = 0;
2425    std::unique_ptr< ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>> BidiStream(::grpc::ClientContext* context) {
26      return std::unique_ptr< ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>>(BidiStreamRaw(context));
27    }
2829    private:
30    virtual ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>* BidiStreamRaw(::grpc::ClientContext* context) = 0;
3132  } // End StubInterface
3334} // End EchoTestService
35```
36
37
38If we mock the StubInterface and set expectations on the pure-virtual methods we can test client-side logic without having to make any rpcs.
39
40A mock for this StubInterface will look like this:
41
42
43```c
44class MockEchoTestServiceStub : public EchoTestService::StubInterface {
45 public:
46  MOCK_METHOD3(Echo, ::grpc::Status(::grpc::ClientContext* context, const ::grpc::testing::EchoRequest& request, ::grpc::testing::EchoResponse* response));
47  MOCK_METHOD1(BidiStreamRaw, ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>*(::grpc::ClientContext* context));
48};
49```
50
51
52**Generating mock code:**
53
54Such a mock can be auto-generated by:
55
56
57
581.  Setting flag(generate_mock_code=true) on grpc plugin for protoc, or
591.  Setting an attribute(generate_mocks) in your bazel rule.
60
61Protoc plugin flag:
62
63```sh
64protoc -I . --grpc_out=generate_mock_code=true:. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` echo.proto
65```
66
67Bazel rule:
68
69```py
70grpc_proto_library(
71  name = "echo_proto",
72  srcs = ["echo.proto"],
73  generate_mocks = True,
74)
75```
76
77
78By adding such a flag now a header file `echo_mock.grpc.pb.h` containing the mocked stub will also be generated.
79
80This header file can then be included in test files along with a gmock dependency.
81
82**Writing tests with mocked Stub.**
83
84Consider the following client a user might have:
85
86```c
87class FakeClient {
88 public:
89  explicit FakeClient(EchoTestService::StubInterface* stub) : stub_(stub) {}
90
91  void DoEcho() {
92    ClientContext context;
93    EchoRequest request;
94    EchoResponse response;
95    request.set_message("hello world");
96    Status s = stub_->Echo(&context, request, &response);
97    EXPECT_EQ(request.message(), response.message());
98    EXPECT_TRUE(s.ok());
99  }
100
101  void DoBidiStream() {
102    EchoRequest request;
103    EchoResponse response;
104    ClientContext context;
105    grpc::string msg("hello");
106
107    std::unique_ptr<ClientReaderWriterInterface<EchoRequest, EchoResponse>>
108        stream = stub_->BidiStream(&context);
109
110    request.set_message(msg  "0");
111    EXPECT_TRUE(stream->Write(request));
112    EXPECT_TRUE(stream->Read(&response));
113    EXPECT_EQ(response.message(), request.message());
114
115    request.set_message(msg  "1");
116    EXPECT_TRUE(stream->Write(request));
117    EXPECT_TRUE(stream->Read(&response));
118    EXPECT_EQ(response.message(), request.message());
119
120    request.set_message(msg  "2");
121    EXPECT_TRUE(stream->Write(request));
122    EXPECT_TRUE(stream->Read(&response));
123    EXPECT_EQ(response.message(), request.message());
124
125    stream->WritesDone();
126    EXPECT_FALSE(stream->Read(&response));
127
128    Status s = stream->Finish();
129    EXPECT_TRUE(s.ok());
130  }
131
132  void ResetStub(EchoTestService::StubInterface* stub) { stub_ = stub; }
133
134 private:
135  EchoTestService::StubInterface* stub_;
136};
137```
138
139A test could initialize this FakeClient with a mocked stub having set expectations on it:
140
141Unary RPC:
142
143```c
144MockEchoTestServiceStub stub;
145EchoResponse resp;
146resp.set_message("hello world");
147Expect_CALL(stub, Echo(_,_,_)).Times(Atleast(1)).WillOnce(DoAll(SetArgPointee<2>(resp), Return(Status::OK)));
148FakeClient client(stub);
149client.DoEcho();
150```
151
152Streaming RPC:
153
154```c
155ACTION_P(copy, msg) {
156  arg0->set_message(msg->message());
157}
158
159
160auto rw = new MockClientReaderWriter<EchoRequest, EchoResponse>();
161EchoRequest msg;
162EXPECT_CALL(*rw, Write(_, _)).Times(3).WillRepeatedly(DoAll(SaveArg<0>(&msg), Return(true)));
163EXPECT_CALL(*rw, Read(_)).
164      WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))).
165      WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))).
166      WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))).
167      WillOnce(Return(false));
168
169MockEchoTestServiceStub  stub;
170EXPECT_CALL(stub, BidiStreamRaw(_)).Times(AtLeast(1)).WillOnce(Return(rw));
171
172FakeClient client(stub);
173client.DoBidiStream();
174```
175
176