From 074dbfbc526bcf87e684e25547ef14b386be4020 Mon Sep 17 00:00:00 2001 From: MakMukhi Date: Fri, 19 May 2017 13:36:45 -0700 Subject: [PATCH] Add doc and example for mocking streaming RPCs (#1230) * Example code for mocking streams * Added another expectation on the stream * Documentation for mocking streaming RPCs --- Documentation/gomock-example.md | 63 +++++- .../route_guide/mock_routeguide/rg_mock.go | 200 ++++++++++++++++++ .../mock_routeguide/rg_mock_test.go | 64 ++++++ 3 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 examples/route_guide/mock_routeguide/rg_mock.go create mode 100644 examples/route_guide/mock_routeguide/rg_mock_test.go diff --git a/Documentation/gomock-example.md b/Documentation/gomock-example.md index fa634a79..54743e89 100644 --- a/Documentation/gomock-example.md +++ b/Documentation/gomock-example.md @@ -1,6 +1,8 @@ # Mocking Service for gRPC -[Example code](https://github.com/grpc/grpc-go/tree/master/examples/helloworld/mock_helloworld) +[Example code unary RPC](https://github.com/grpc/grpc-go/tree/master/examples/helloworld/mock_helloworld) + +[Example code streaming RPC](https://github.com/grpc/grpc-go/tree/master/examples/route_guide/mock_routeguide) ## Why? @@ -117,5 +119,64 @@ mockGreeterClient.EXPECT().SayHello( ).Return(&helloworld.HelloReply{Message: "Mocked Interface"}, nil) ``` +## Mock streaming RPCs: +For our example we consider the case of bi-directional streaming RPCs. Concretely, we'll write a test for RouteChat function from the route guide example to demonstrate how to write mocks for streams. +RouteChat is a bi-directional streaming RPC, which means calling RouteChat returns a stream that can __Send__ and __Recv__ messages to and from the server, respectively. We'll start by creating a mock of this stream interface returned by RouteChat and then we'll mock the client interface and set expectation on the method RouteChat to return our mocked stream. + +### Generating mocking code: +Like before we'll use [mockgen](https://github.com/golang/mock#running-mockgen). From the `examples/route_guide` directory run: `mockgen google.golang.org/grpc/examples/route_guide/routeguide RouteGuideClient,RouteGuide_RouteChatClient > mock_route_guide/rg_mock.go` + +Notice that we are mocking both client(`RouteGuideClient`) and stream(`RouteGuide_RouteChatClient`) interfaces here. + +This will create a file `rg_mock.go` under directory `mock_route_guide`. This file contins all the mocking code we need to write our test. + +In our test code, like before, we import the this mocking code along with the generated code + +```go +import ( + rgmock "google.golang.org/grpc/examples/route_guide/mock_routeguide" + rgpb "google.golang.org/grpc/examples/route_guide/routeguide" +) +``` + +Now conside a test that takes the RouteGuide client object as a parameter, makes a RouteChat rpc call and sends a message on the resulting stream. Furthermore, this test expects to see the same message to be received on the stream. + +```go +var msg = ... + +// Creates a RouteChat call and sends msg on it. +// Checks if the received message was equal to msg. +func testRouteChat(client rgb.RouteChatClient) error{ + ... +} +``` + +We can inject our mock in here by simply passing it as an argument to the method. + +Creating mock for stream interface: + +```go + stream := rgmock.NewMockRouteGuide_RouteChatClient(ctrl) +} +``` + +Setting Expectations: + +```go + stream.EXPECT().Send(gomock.Any()).Return(nil) + stream.EXPECT().Recv().Return(msg, nil) +``` + +Creating mock for client interface: + +```go + rgclient := rgmock.NewMockRouteGuideClient(ctrl) +``` + +Setting Expectations: + +```go + rgclient.EXPECT().RouteChat(gomock.Any()).Return(stream, nil) +``` diff --git a/examples/route_guide/mock_routeguide/rg_mock.go b/examples/route_guide/mock_routeguide/rg_mock.go new file mode 100644 index 00000000..328c929f --- /dev/null +++ b/examples/route_guide/mock_routeguide/rg_mock.go @@ -0,0 +1,200 @@ +// Automatically generated by MockGen. DO NOT EDIT! +// Source: google.golang.org/grpc/examples/route_guide/routeguide (interfaces: RouteGuideClient,RouteGuide_RouteChatClient) + +package mock_routeguide + +import ( + gomock "github.com/golang/mock/gomock" + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" + routeguide "google.golang.org/grpc/examples/route_guide/routeguide" + metadata "google.golang.org/grpc/metadata" +) + +// Mock of RouteGuideClient interface +type MockRouteGuideClient struct { + ctrl *gomock.Controller + recorder *_MockRouteGuideClientRecorder +} + +// Recorder for MockRouteGuideClient (not exported) +type _MockRouteGuideClientRecorder struct { + mock *MockRouteGuideClient +} + +func NewMockRouteGuideClient(ctrl *gomock.Controller) *MockRouteGuideClient { + mock := &MockRouteGuideClient{ctrl: ctrl} + mock.recorder = &_MockRouteGuideClientRecorder{mock} + return mock +} + +func (_m *MockRouteGuideClient) EXPECT() *_MockRouteGuideClientRecorder { + return _m.recorder +} + +func (_m *MockRouteGuideClient) GetFeature(_param0 context.Context, _param1 *routeguide.Point, _param2 ...grpc.CallOption) (*routeguide.Feature, error) { + _s := []interface{}{_param0, _param1} + for _, _x := range _param2 { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "GetFeature", _s...) + ret0, _ := ret[0].(*routeguide.Feature) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockRouteGuideClientRecorder) GetFeature(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetFeature", _s...) +} + +func (_m *MockRouteGuideClient) ListFeatures(_param0 context.Context, _param1 *routeguide.Rectangle, _param2 ...grpc.CallOption) (routeguide.RouteGuide_ListFeaturesClient, error) { + _s := []interface{}{_param0, _param1} + for _, _x := range _param2 { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "ListFeatures", _s...) + ret0, _ := ret[0].(routeguide.RouteGuide_ListFeaturesClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockRouteGuideClientRecorder) ListFeatures(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "ListFeatures", _s...) +} + +func (_m *MockRouteGuideClient) RecordRoute(_param0 context.Context, _param1 ...grpc.CallOption) (routeguide.RouteGuide_RecordRouteClient, error) { + _s := []interface{}{_param0} + for _, _x := range _param1 { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "RecordRoute", _s...) + ret0, _ := ret[0].(routeguide.RouteGuide_RecordRouteClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockRouteGuideClientRecorder) RecordRoute(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0}, arg1...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "RecordRoute", _s...) +} + +func (_m *MockRouteGuideClient) RouteChat(_param0 context.Context, _param1 ...grpc.CallOption) (routeguide.RouteGuide_RouteChatClient, error) { + _s := []interface{}{_param0} + for _, _x := range _param1 { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "RouteChat", _s...) + ret0, _ := ret[0].(routeguide.RouteGuide_RouteChatClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockRouteGuideClientRecorder) RouteChat(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0}, arg1...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "RouteChat", _s...) +} + +// Mock of RouteGuide_RouteChatClient interface +type MockRouteGuide_RouteChatClient struct { + ctrl *gomock.Controller + recorder *_MockRouteGuide_RouteChatClientRecorder +} + +// Recorder for MockRouteGuide_RouteChatClient (not exported) +type _MockRouteGuide_RouteChatClientRecorder struct { + mock *MockRouteGuide_RouteChatClient +} + +func NewMockRouteGuide_RouteChatClient(ctrl *gomock.Controller) *MockRouteGuide_RouteChatClient { + mock := &MockRouteGuide_RouteChatClient{ctrl: ctrl} + mock.recorder = &_MockRouteGuide_RouteChatClientRecorder{mock} + return mock +} + +func (_m *MockRouteGuide_RouteChatClient) EXPECT() *_MockRouteGuide_RouteChatClientRecorder { + return _m.recorder +} + +func (_m *MockRouteGuide_RouteChatClient) CloseSend() error { + ret := _m.ctrl.Call(_m, "CloseSend") + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) CloseSend() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "CloseSend") +} + +func (_m *MockRouteGuide_RouteChatClient) Context() context.Context { + ret := _m.ctrl.Call(_m, "Context") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) Context() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Context") +} + +func (_m *MockRouteGuide_RouteChatClient) Header() (metadata.MD, error) { + ret := _m.ctrl.Call(_m, "Header") + ret0, _ := ret[0].(metadata.MD) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) Header() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Header") +} + +func (_m *MockRouteGuide_RouteChatClient) Recv() (*routeguide.RouteNote, error) { + ret := _m.ctrl.Call(_m, "Recv") + ret0, _ := ret[0].(*routeguide.RouteNote) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) Recv() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Recv") +} + +func (_m *MockRouteGuide_RouteChatClient) RecvMsg(_param0 interface{}) error { + ret := _m.ctrl.Call(_m, "RecvMsg", _param0) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) RecvMsg(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "RecvMsg", arg0) +} + +func (_m *MockRouteGuide_RouteChatClient) Send(_param0 *routeguide.RouteNote) error { + ret := _m.ctrl.Call(_m, "Send", _param0) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) Send(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Send", arg0) +} + +func (_m *MockRouteGuide_RouteChatClient) SendMsg(_param0 interface{}) error { + ret := _m.ctrl.Call(_m, "SendMsg", _param0) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) SendMsg(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "SendMsg", arg0) +} + +func (_m *MockRouteGuide_RouteChatClient) Trailer() metadata.MD { + ret := _m.ctrl.Call(_m, "Trailer") + ret0, _ := ret[0].(metadata.MD) + return ret0 +} + +func (_mr *_MockRouteGuide_RouteChatClientRecorder) Trailer() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Trailer") +} diff --git a/examples/route_guide/mock_routeguide/rg_mock_test.go b/examples/route_guide/mock_routeguide/rg_mock_test.go new file mode 100644 index 00000000..247399bc --- /dev/null +++ b/examples/route_guide/mock_routeguide/rg_mock_test.go @@ -0,0 +1,64 @@ +package mock_routeguide_test + +import ( + "fmt" + "testing" + + "github.com/golang/mock/gomock" + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + rgmock "google.golang.org/grpc/examples/route_guide/mock_routeguide" + rgpb "google.golang.org/grpc/examples/route_guide/routeguide" +) + +var ( + msg = &rgpb.RouteNote{ + Location: &rgpb.Point{Latitude: 17, Longitude: 29}, + Message: "Taxi-cab", + } +) + +func TestRouteChat(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Create mock for the stream returned by RouteChat + stream := rgmock.NewMockRouteGuide_RouteChatClient(ctrl) + // set expectation on sending. + stream.EXPECT().Send( + gomock.Any(), + ).Return(nil) + // Set expectation on receiving. + stream.EXPECT().Recv().Return(msg, nil) + stream.EXPECT().CloseSend().Return(nil) + // Create mock for the client interface. + rgclient := rgmock.NewMockRouteGuideClient(ctrl) + // Set expectation on RouteChat + rgclient.EXPECT().RouteChat( + gomock.Any(), + ).Return(stream, nil) + if err := testRouteChat(rgclient); err != nil { + t.Fatalf("Test failed: %v", err) + } +} + +func testRouteChat(client rgpb.RouteGuideClient) error { + stream, err := client.RouteChat(context.Background()) + if err != nil { + return err + } + if err := stream.Send(msg); err != nil { + return err + } + if err := stream.CloseSend(); err != nil { + return err + } + got, err := stream.Recv() + if err != nil { + return err + } + if !proto.Equal(got, msg) { + return fmt.Errorf("stream.Recv() = %v, want %v", got, msg) + } + return nil +}