aboutsummaryrefslogtreecommitdiffhomepage
path: root/doc/unit_testing.md
blob: 0aa9be9b9dc71df75e09eb5d7ed4ad0c590453d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# How to write unit tests for gRPC C client.

tl;dr: [Example code](https://github.com/grpc/grpc/blob/master/test/cpp/end2end/mock_test.cc).

To 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.

For instance, consider an EchoService like this:


```proto
service EchoTestService {
        rpc Echo(EchoRequest) returns (EchoResponse);
        rpc BidiStream(stream EchoRequest) returns (stream EchoResponse);
}
```

The code generated would look something like this:

```c
class EchoTestService final {
  public:
  class StubInterface {
    virtual ::grpc::Status Echo(::grpc::ClientContext* context, const ::grpc::testing::EchoRequest& request, ::grpc::testing::EchoResponse* response) = 0;
  …
    std::unique_ptr< ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>> BidiStream(::grpc::ClientContext* context) {
      return std::unique_ptr< ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>>(BidiStreamRaw(context));
    }
  …
    private:
    virtual ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>* BidiStreamRaw(::grpc::ClientContext* context) = 0;
  …
  } // End StubInterface
…
} // End EchoTestService
```


If we mock the StubInterface and set expectations on the pure-virtual methods we can test client-side logic without having to make any rpcs.

A mock for this StubInterface will look like this:


```c
class MockEchoTestServiceStub : public EchoTestService::StubInterface {
 public:
  MOCK_METHOD3(Echo, ::grpc::Status(::grpc::ClientContext* context, const ::grpc::testing::EchoRequest& request, ::grpc::testing::EchoResponse* response));
  MOCK_METHOD1(BidiStreamRaw, ::grpc::ClientReaderWriterInterface< ::grpc::testing::EchoRequest, ::grpc::testing::EchoResponse>*(::grpc::ClientContext* context));
};
```


**Generating mock code:**

Such a mock can be auto-generated by:



1.  Setting flag(generate_mock_code=true) on grpc plugin for protoc, or
1.  Setting an attribute(generate_mock) in your bazel rule.

Protoc plugin flag:

```sh
protoc -I . --grpc_out=generate_mock_code=true:. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` echo.proto
```

Bazel rule:

```py
grpc_proto_library(
  name = "echo_proto",
  srcs = ["echo.proto"],
  generate_mock = True, 
)
```


By adding such a flag now a header file `echo_mock.grpc.pb.h` containing the mocked stub will also be generated. 

This header file can then be included in test files along with a gmock dependency.

**Writing tests with mocked Stub.**

Consider the following client a user might have:

```c
class FakeClient {
 public:
  explicit FakeClient(EchoTestService::StubInterface* stub) : stub_(stub) {}

  void DoEcho() {
    ClientContext context;
    EchoRequest request;
    EchoResponse response;
    request.set_message("hello world");
    Status s = stub_->Echo(&context, request, &response);
    EXPECT_EQ(request.message(), response.message());
    EXPECT_TRUE(s.ok());
  }

  void DoBidiStream() {
    EchoRequest request;
    EchoResponse response;
    ClientContext context;
    grpc::string msg("hello");

    std::unique_ptr<ClientReaderWriterInterface<EchoRequest, EchoResponse>>
        stream = stub_->BidiStream(&context);

    request.set_message(msg  "0");
    EXPECT_TRUE(stream->Write(request));
    EXPECT_TRUE(stream->Read(&response));
    EXPECT_EQ(response.message(), request.message());

    request.set_message(msg  "1");
    EXPECT_TRUE(stream->Write(request));
    EXPECT_TRUE(stream->Read(&response));
    EXPECT_EQ(response.message(), request.message());

    request.set_message(msg  "2");
    EXPECT_TRUE(stream->Write(request));
    EXPECT_TRUE(stream->Read(&response));
    EXPECT_EQ(response.message(), request.message());

    stream->WritesDone();
    EXPECT_FALSE(stream->Read(&response));

    Status s = stream->Finish();
    EXPECT_TRUE(s.ok());
  }

  void ResetStub(EchoTestService::StubInterface* stub) { stub_ = stub; }

 private:
  EchoTestService::StubInterface* stub_;
};
```

A test could initialize this FakeClient with a mocked stub having set expectations on it:

Unary RPC:

```c
MockEchoTestServiceStub stub;
EchoResponse resp;
resp.set_message("hello world");
Expect_CALL(stub, Echo(_,_,_)).Times(Atleast(1)).WillOnce(DoAll(SetArgPointee<2>(resp), Return(Status::OK)));
FakeClient client(stub);
client.DoEcho();
```

Streaming RPC:

```c
ACTION_P(copy, msg) {
  arg0->set_message(msg->message());
}


auto rw = new MockClientReaderWriter<EchoRequest, EchoResponse>();
EchoRequest msg;
EXPECT_CALL(*rw, Write(_, _)).Times(3).WillRepeatedly(DoAll(SaveArg<0>(&msg), Return(true)));
EXPECT_CALL(*rw, Read(_)).
      WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))).
      WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))).
      WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))).
      WillOnce(Return(false));

MockEchoTestServiceStub  stub;
EXPECT_CALL(stub, BidiStreamRaw(_)).Times(AtLeast(1)).WillOnce(Return(rw));

FakeClient client(stub);
client.DoBidiStream();
```