|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- # Mocking Service for gRPC
-
- [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?
-
- To test client-side logic without the overhead of connecting to a real server. Mocking enables users to write light-weight unit tests to check functionalities on client-side without invoking RPC calls to a server.
-
- ## Idea: Mock the client stub that connects to the server.
-
- We use Gomock to mock the client interface (in the generated code) and programmatically set its methods to expect and return pre-determined values. This enables users to write tests around the client logic and use this mocked stub while making RPC calls.
-
- ## How to use Gomock?
-
- Documentation on Gomock can be found [here](https://github.com/golang/mock).
- A quick reading of the documentation should enable users to follow the code below.
-
- Consider a gRPC service based on following proto file:
-
- ```proto
- //helloworld.proto
-
- package helloworld;
-
- message HelloRequest {
- string name = 1;
- }
-
- message HelloReply {
- string name = 1;
- }
-
- service Greeter {
- rpc SayHello (HelloRequest) returns (HelloReply) {}
- }
- ```
-
- The generated file helloworld.pb.go will have a client interface for each service defined in the proto file. This interface will have methods corresponding to each rpc inside that service.
-
- ```Go
- type GreeterClient interface {
- SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
- }
- ```
-
- The generated code also contains a struct that implements this interface.
-
- ```Go
- type greeterClient struct {
- cc *grpc.ClientConn
- }
- func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error){
- // ...
- // gRPC specific code here
- // ...
- }
- ```
-
- Along with this the generated code has a method to create an instance of this struct.
- ```Go
- func NewGreeterClient(cc *grpc.ClientConn) GreeterClient
- ```
-
- The user code uses this function to create an instance of the struct greeterClient which then can be used to make rpc calls to the server.
- We will mock this interface GreeterClient and use an instance of that mock to make rpc calls. These calls instead of going to server will return pre-determined values.
-
- To create a mock we’ll use [mockgen](https://github.com/golang/mock#running-mockgen).
- From the directory ``` examples/helloworld/ ``` run ``` mockgen google.golang.org/grpc/examples/helloworld/helloworld GreeterClient > mock_helloworld/hw_mock.go ```
-
- Notice that in the above command we specify GreeterClient as the interface to be mocked.
-
- The user test code can import the package generated by mockgen along with library package gomock to write unit tests around client-side logic.
- ```Go
- import "github.com/golang/mock/gomock"
- import hwmock "google.golang.org/grpc/examples/helloworld/mock_helloworld"
- ```
-
- An instance of the mocked interface can be created as:
- ```Go
- mockGreeterClient := hwmock.NewMockGreeterClient(ctrl)
- ```
- This mocked object can be programmed to expect calls to its methods and return pre-determined values. For instance, we can program mockGreeterClient to expect a call to its method SayHello and return a HelloReply with message “Mocked RPC”.
-
- ```Go
- mockGreeterClient.EXPECT().SayHello(
- gomock.Any(), // expect any value for first parameter
- gomock.Any(), // expect any value for second parameter
- ).Return(&helloworld.HelloReply{Message: “Mocked RPC”}, nil)
- ```
-
- gomock.Any() indicates that the parameter can have any value or type. We can indicate specific values for built-in types with gomock.Eq().
- However, if the test code needs to specify the parameter to have a proto message type, we can replace gomock.Any() with an instance of a struct that implements gomock.Matcher interface.
-
- ```Go
- type rpcMsg struct {
- msg proto.Message
- }
-
- func (r *rpcMsg) Matches(msg interface{}) bool {
- m, ok := msg.(proto.Message)
- if !ok {
- return false
- }
- return proto.Equal(m, r.msg)
- }
-
- func (r *rpcMsg) String() string {
- return fmt.Sprintf("is %s", r.msg)
- }
-
- ...
-
- req := &helloworld.HelloRequest{Name: "unit_test"}
- mockGreeterClient.EXPECT().SayHello(
- gomock.Any(),
- &rpcMsg{msg: req},
- ).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 contains 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 considering 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)
- ```
|