Merge branch 'mmukhi_gomock_example' of https://github.com/MakMukhi/grpc-go into mmukhi_gomock_example
This commit is contained in:
121
Documentation/gomock-example.md
Normal file
121
Documentation/gomock-example.md
Normal file
@ -0,0 +1,121 @@
|
||||
# Mocking Service for gRPC
|
||||
|
||||
Example code: examples/helloworld/mock
|
||||
|
||||
## 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/mock/ ``` 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/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)
|
||||
```
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user