GRPC Core  4.0.0
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
How to write unit tests for gRPC C client.

tl;dr: Example code.

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_pluginecho.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(); ```