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
This commit is contained in:
MakMukhi
2017-05-19 13:36:45 -07:00
committed by Menghan Li
parent 11d93ecdb9
commit 074dbfbc52
3 changed files with 326 additions and 1 deletions

View File

@ -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)
```

View File

@ -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")
}

View File

@ -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
}