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:
service EchoTestService { rpc Echo(EchoRequest) returns (EchoResponse); rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); }
The code generated would look something like this:
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:
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:
Protoc plugin flag:
protoc -I . --grpc_out=generate_mock_code=true:. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` echo.proto
Bazel rule:
grpc_proto_library( name = "echo_proto", srcs = ["echo.proto"], generate_mocks = 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:
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:
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:
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();