aboutsummaryrefslogtreecommitdiffhomepage
path: root/doc/unit_testing.md
diff options
context:
space:
mode:
authorGravatar MakMukhi <mmukhi@google.com>2017-05-03 17:50:31 -0700
committerGravatar GitHub <noreply@github.com>2017-05-03 17:50:31 -0700
commit7431a78628ffcd86fd7e7512610ad118c16eb6f1 (patch)
treea8d2208488bcfab68790f5fcd450a7ea5ab281ae /doc/unit_testing.md
parent4deb397d9fd74d7d8e5c4064b5cf43f618c4a912 (diff)
How to write unit tests for client sync APIs.
Diffstat (limited to 'doc/unit_testing.md')
-rw-r--r--doc/unit_testing.md174
1 files changed, 174 insertions, 0 deletions
diff --git a/doc/unit_testing.md b/doc/unit_testing.md
index 8b13789179..3307d11587 100644
--- a/doc/unit_testing.md
+++ b/doc/unit_testing.md
@@ -1 +1,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 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 generate 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.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();
+```