mock grpc client request and response using testify - unit-testing

I have been trying to mock my grpc client/server to control the response.
what I'm trying to achieve is the flex testify give us with mocks, and using the once(),times() idea.
so I will explain further lets say I have a go program that run the following:
I want to control each response of the iteration of client.getUser()
type DB struct {
api serviceAPI
}
type service struct {
}
type serviceAPI interface {
getClient() (pb.UserServiceClient, *grpc.ClientConn, error)
}
func (db *DB) getNextUser()(*UserAcc,error){
client := db.api.getClient()
var index uint64 = 0
for {
user = client.getUser(context.Background(), &pb.GetUserRequest{Index: index})
if(user == nil){
return nil,nil
}
if user(!=nil){
fmt.Printl(user)
}
}
}
func (s service) getClient() (pb.UserServiceClient, *grpc.ClientConn, error) {
addr := GetAgentAddress()
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
client := pb.NewUserServiceClient(conn)
return client, conn, nil
}
proto.go
message GetUserRequest{
uint64 index = 1;
}
message GetUserResponse{
bytes user = 1;
}
service userService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}
user_grpc.pb.go
type UserServiceClient interface {
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error)
UpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*UpdateUserResponse, error)
main_test.go
type MainTestSuite struct {
suite.Suite
}
type serviceMock struct {
mock.Mock
}
type clientMock struct {
mock.Mock
}
func (c *clientMock) UpdateUser(ctx context.Context, in *pb.UpdateUserRequest, opts ...grpc.CallOption) (*pb.UpdateUserResponse, error) {
//TODO implement me
panic("implement me")
}
func (c *clientMock) GetUser(ctx context.Context, in *pb.GetUserRequest, opts ...grpc.CallOption) (*pb.GetUserResponse, error) {
args := c.Called(ctx, in, opts)
return args.Get(0).(*pb.GetUserResponse), args.Error(1)
}
func (service *serviceMock) getClient() (pb.UserServiceClient, *grpc.ClientConn, error) {
args := service.Called()
return args.Get(0).(clientMock), args.Get(1).(*grpc.ClientConn), args.Error(2)
}
func (suite *MainTestSuite) TestGetNextUser() {
t := suite.T()
t.Run("Should successfully get the next User", func(t *testing.T) {
mServiceApi := serviceMock{}
ClientMock := clientMock{}
mServiceApi.On("getClient").Return(ClientMock, nil, nil)
ClientMock.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(&pb.GetUserResponse{
User: []bytes("something"),
}, nil).once()
ClientMock.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(&pb.GetUserResponse{
User: []bytes("some other user"),
}, nil).once()
ClientMock.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(&pb.GetUserResponse{
User: []bytes("something"),
}, nil).once()
db := DB{
api: &mServiceApi,
}
nextUser ,_ := db.getNextUser(true)
assert.Nil(t, nextUser)
})
}
I would like for each iteration of the GetUser command of the client grpc to get different answers using the once() or times() of testify
am I'm mocking the grpc client the right way?
right now i get the following issues:
Cannot use 'args.Get(0).(clientMock)' (type clientMock) as the type pb.UserServiceClient Type does not implement 'pb.UserServiceClient' as the 'UpdateUser' method has a pointer receiver.
any idea why?

I managed to do it with the mockery package and install it by brew
mockery --name=UserServiceClient --unroll-variadic=False

Related

How do I mock netconf session in unit tests Golang

I am using juniper's netconf package ("github.com/Juniper/go-netconf/netconf") to establish a netconf session in my code.
I wanted to know how can I mock a netconf session in my unit tests.
My methods are:
func TestMyFunction(t *testing.T) {
getSSHConnection = mockGetSSHConnection
got := MyFunction()
want := 123
if !reflect.DeepEqual(got, want) {
t.Errorf("Error expectation not met, want %v, got %v", want, got)
}
}
func mockGetSSHConnection() (*netconf.Session, error) {
var sess netconf.Session
sess.SessionID = 123
return &sess, nil
}
The problem arises when MyFunction() has a line that defers sess.Close() and it's throwing error due to nil pointer dereference
func MyFunction() int {
sess, err := getSSHConnection() // returns (*netconf.Session, error)
if err == nil && sess != nil {
defer sess.Close() -> Problem happens here
// Calls RPC here and rest of the code here
}
return 0
}
So, what changes can I make on mockGetSSHConnection() method so that sess.Close() won't throw error?
The nil pointer error originates within the Close function when Close is called on the underlying Transport. Fortunately Transport is an interface type that you can easily mock and use in an actual instance of the netconf.Session. For example like so:
type MockTransport struct{}
func (t *MockTransport) Send([]byte) error {
return nil
}
func (t *MockTransport) Receive() ([]byte, error) {
return []byte{}, nil
}
func (t *MockTransport) Close() error {
return nil
}
func (t *MockTransport) ReceiveHello() (*netconf.HelloMessage, error) {
return &netconf.HelloMessage{SessionID: 123}, nil
}
func (t *MockTransport) SendHello(*netconf.HelloMessage) error {
return nil
}
func (t *MockTransport) SetVersion(version string) {
}
func mockGetSSHConnection() (*netconf.Session, error) {
t := MockTransport{}
sess := netconf.NewSession(&t)
return sess, nil
}
Note that the function you want to test currently return 0 and not the SessionID of the session. So you should fix that before the test is successful.
You could use OOP and "github.com/stretchr/testify/mock" package
for example create
type SshClientMock struct {
mock.Mock
}
func (s *SshClientMock) GetSSHConnection() {
return //what do you need
}
in your unit test:
sshClient := SshClientMock
sshClient.On("GetSSHConnection").Return(what do you need)
and then call your method

Passing mock as argument in Golang test

This is the code I am testing
func listObjects(cli *client.Client, options clientOptions) ([]BlobObjects, error) {
objects, err := cli.ListBlobObjects(...)
}
In my test setup I do this
type MockClient struct {
MockListBlobObjects func() ([]BlobObjects, error)
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
And this is my test case
func TestBlobObjects(t *testing.T) {
tests := map[string]struct {
client *MockClient
...
}{
"Test case 1": {
client: &MockClient{
MockListBlobObjects: ....,
},
....
},
....
for testName, test := range tests {
blobs, err := (test.client, clientOptions{})
// make assertions here
}
The problem is test.client. Compiler is telling me
cannot use test.client (variable of type *MockClient) as *client.Client value in argument to listObjects
My hope was I have a mock client and if I call the function under test, then the mock client passed will call the mocked listObjects. This is how I would do in Python.
What should I do in Golang?
You need to create a common interface, which will be implemented by both types
type Client interface {
ListBlobObjects func() ([]BlobObjects, error)
}
type RealClient struct {
}
func (c *RealClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some response
}
type MockClient struct {
// note: don't put method signature in function body
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
and then
var client client.Client
client = &RealClient{}
// or
client = &MockClient{}

How to mock AWS DynamoDB if multiple GetItem() in flow?

eventHandler.go
func eventHandler(Id) error {
eventDetails, err := GetEventStore(svc.Connection.DynamoDBClient, Id)
if err != nil {
log.Error("Error while reading Store", err)
return err
}
if eventDetails.status == "completed" {
config, err := GetConfig(svc.Connection.DynamoDBClient, Key)
if err != nil {
log.Error("Error while reading Config Store", err)
return err
}
// process it
}
return nil
}
Most of our data is stored in stores so while writing unit tests for eventHandler how to mockDynamoDB with multiple GetItemOutput ?
eventHnadler_test.go
...
eventDetails := {
//data
}
config := {
//data
}
DB := client.DynamoDBClient{
Client: mockDynamoDB{
Output: dynamodb.GetItemOutput{
Item: eventDetails,
},
Error: tc.Error,
},
}
...
Can multiple get calls be mocked in GO aws sdk v2?
Because AWS dynamo DB got restructured so
github.com/aws/aws-sdk-go-v2/service/dynamodb/dynamodbiface doesn't exists anymore you have to implement your own interface to have proper dynamo mocking
your Wrapper around aws implentation should be something similar like that
type Dynamodb struct {
Client DynamoAPI
}
type DynamoAPI interface {
GetItem(ctx context.Context, params *dynamodb.GetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error)
UpdateItem(ctx context.Context, params *dynamodb.UpdateItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error)
PutItem(ctx context.Context, params *dynamodb.PutItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error)
DeleteItem(ctx context.Context, params *dynamodb.DeleteItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error)
Query(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error)
Scan(ctx context.Context, params *dynamodb.ScanInput, optFns ...func(*dynamodb.Options)) (*dynamodb.ScanOutput, error)
}
and here could be your mocks
type mockDynamodb struct {
rbaws.DynamoAPI
*dynamodb.Client
clientError error
}
func (m mockDynamodb) GetItem(ctx context.Context, params *dynamodb.GetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error) {
return &dynamodb.GetItemOutput{
Item: map[string]types.AttributeValue{},
}, m.clientError
}
func (m mockDynamodb) GetItem(ctx context.Context, params *dynamodb.GetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error) {
if (params.tablename == “eventDetails”) {
return m.eventDetailsItemOutput, m.clientError
} else {
return m.configItemOutput, m.clientError
}
}
In test file
DB := client.DynamoDBClient{
Client: mockDynamoDB{
eventDetailsItemOutput: dynamodb.GetItemOutput{
Item: eventDetails,
},
configItemOutput: dynamodb.GetItemOutput{
Item: eventDetails,
},
Error: tc.Error,
},
}

How to write Unit test in Golang usng echo for end point url using go mock-gen mocking?

I write this go code for login. Now i want to unit test my code. This code is depends on controller to service layer then service to repository layer. I want to use gomock tool for mocking, if any other please suggest me. I'm using echo framework.
Here
serializers.LoginReq =
{
Email string,
Phone string,
Admin bool
}
type auth struct {
authSvc svc.IAuth
userSvc svc.IUsers
}
func NewAuthController(grp interface {}, authSvc svc.IAuth, userSvc svc.IUsers) {
ac: = & auth {
authSvc: authSvc,
userSvc: userSvc,
}
g: = grp.( * echo.Group)
g.POST("/v1/login", ac.Login)
}
func(ctr * auth) Login(c echo.Context) error {
var cred * serializers.LoginReq
var resp * serializers.LoginResp
var err error
if err = c.Bind( & cred) err != nil {
return c.JSON(err.Status, err)
}
if resp, err = ctr.authSvc.Login(cred); err != nil {
return c.JSON(err.Status, err)
}
return c.JSON(http.StatusOK, resp)
}
Use dependency injection. Dependency injection is a design pattern that decouples dependencies between two or more layers of software.
How it works
Pass a dependency to the Login function. In Go, the dependency is often an interface type. Interfaces express generalizations or abstractions about the behaviors of other types. A type satisfies an interface if it has all the methods in the interface. With an interface, you can replace a real object with a fake one (a mock) in your tests. This works without Go's type system complaining as long as a concrete type satisfies the interface.
type Auther interface {
Login(cred *serializers.LoginReq) (*serializers.LoginResp, error)
}
Go Interfaces are satisfied implicitly.
// auth service must implement the Auther interface
type auth struct {
authSvc Auther
}
// route handler
func(ctr *auth) Login(c echo.Context) error {
var cred * serializers.LoginReq
var resp * serializers.LoginResp
var err error
if err = c.Bind( & cred) err != nil {
return c.JSON(err.Status, err)
}
// the function signature of the service-level Login method must match the interface
if resp, err = ctr.authSvc.Login(cred); err != nil {
return c.JSON(err.Status, err)
}
return c.JSON(http.StatusOK, resp)
}
I like using testify/mock library. Create a Mock.
type MockAuth struct {
mock.Mock
}
func (m *MockAuth) Login(cred *serializers.LoginReq) (*serializers.LoginResp, error) {
args := m.Called(cred)
return args.Get(0).(*serializers.LoginResp), args.Error(1)
}
That's it. Just create a test.
func TestLogin (t *testing.T) {
// setup mocks
cred := &serializers.LoginReq{}
mockReturn := &serializers.LoginResp{}
mockAuth := &MockAuth{}
// setup expectation
mockAuth.On("Login", cred).Return(mockReturn, nil)
// setup server
mux := http.NewServeMux()
mux.HandleFunc("/v1/login", func(w http.ResponseWriter, r *http.Request) {
ec := echo.Context{}
ctr: = &auth {
authSvc: mockAuth
}
ctr.Login(ec)
})
// make request
writer := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodPost, "/v1/login", "password")
mux.ServeHTTP(writer, request)
// make assertions
mockAuth.AssertExpectations(t)
}
The code above is not 100% correct. I don't use echo myself, however it should get you close. Hope this helps.

How to stub a method inside another

I'm writing a web app that will send requests to a third-party service to do some calculations, and send it back to the fronted.
Here are the relevant parts for the test I'm trying to writer.
client.go
func (c *ClientResponse) GetBankAccounts() (*BankAccounts, *RequestError) {
req, _ := http.NewRequest("GET", app.BuildUrl("bank_accounts"), nil)
params := req.URL.Query()
params.Add("view", "standard_bank_accounts")
req.URL.RawQuery = params.Encode()
c.ClientDo(req)
if c.Err.Errors != nil {
return nil, c.Err
}
bankAccounts := new(BankAccounts)
defer c.Response.Body.Close()
if err := json.NewDecoder(c.Response.Body).Decode(bankAccounts); err != nil {
return nil, &RequestError{Errors: &Errors{Error{Message: "failed to decode Bank Account response body"}}}
}
return bankAccounts, nil
}
helper.go
type ClientResponse struct {
Response *http.Response
Err *RequestError
}
type ClientI interface {
ClintDo(req *http.Request) (*http.Response, *RequestError)
}
func (c *ClientResponse) ClientDo(req *http.Request) {
//Do some authentication with third-party service
errResp := *new(RequestError)
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
// Here I'm repourposing the third-party service's error response mapping
errResp.Errors.Error.Message = "internal server error. failed create client.Do"
}
c.Response = resp
c.Err = &errResp
}
I only want to test the GetBankAccounts() method so I want to stub the ClientDo, but I'm at a loss on how to do that. Here's what I have so far with my test case.
client_test.go
type StubClientI interface {
ClintDo(req *http.Request) (*http.Response, *RequestError)
}
type StubClientResponse struct {}
func (c *StubClientResponse) ClientDo(req *http.Request) (*http.Response, *RequestError) {
return nil, nil
}
func TestGetBankAccounts(t *testing.T) {
cr := new(ClientResponse)
accounts, err := cr.GetBankAccounts()
if err != nil {
t.Fatal(err.Errors)
}
t.Log(accounts)
}
The ClintDo still pointing to the actual method on the helper.go, how can I make it use the on in the test?
Update:
I've also tried the following and this doesn't work either, it still sends the request to actual third-party service.
client_test.go
func TestGetBankAccounts(t *testing.T) {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, toJson(append(BankAccounts{}.BankAccounts, BankAccount{
Url: "https://foo.bar/v2/bank_accounts/1234",
Name: "Test Bank",
})))
}))
server := httptest.NewServer(mux)
cr := new(ClientResponse)
cr.Client = server.Client()
accounts, err := cr.GetBankAccounts()
if err != nil {
t.Fatal(err.Errors)
}
t.Log(accounts)
}
helper.go
type ClientResponse struct {
Client *http.Client
Response *http.Response
Err *RequestError
}
type ClientI interface {
ClintDo(req *http.Request) (*http.Response, *RequestError)
}
func (c *ClientResponse) ClientDo(req *http.Request) {
//Do some authentication with third-party service
errResp := *new(RequestError)
client := c.Client
resp, err := client.Do(req)
if err != nil {
// Here I'm repourposing the third-party service's error response mapping
errResp.Errors.Error.Message = "internal server error. failed create client.Do"
}
c.Response = resp
c.Err = &errResp
}
Update 2
I was able to make some progress from #dm03514 's answer but unfortunately, now I'm getting nil pointer exceptions on the test but not on actual code.
client.go
func (c *ClientResponse) GetBankAccounts() (*BankAccounts, *RequestError) {
req, _ := http.NewRequest("GET", app.BuildUrl("bank_accounts"), nil)
params := req.URL.Query()
params.Add("view", "standard_bank_accounts")
req.URL.RawQuery = params.Encode()
//cr := new(ClientResponse)
c.HTTPDoer.ClientDo(req)
// Panic occurs here
if c.Err.Errors != nil {
return nil, c.Err
}
bankAccounts := new(BankAccounts)
defer c.Response.Body.Close()
if err := json.NewDecoder(c.Response.Body).Decode(bankAccounts); err != nil {
return nil, &RequestError{Errors: &Errors{Error{Message: "failed to decode Bank Account response body"}}}
}
return bankAccounts, nil
}
helper.go
type ClientResponse struct {
Response *http.Response
Err *RequestError
HTTPDoer HTTPDoer
}
type HTTPDoer interface {
//Do(req *http.Request) (*http.Response, *RequestError)
ClientDo(req *http.Request)
}
type ClientI interface {
}
func (c *ClientResponse) ClientDo(req *http.Request) {
// This method hasn't changed
....
}
client_test.go
type StubDoer struct {
*ClientResponse
}
func (s *StubDoer) ClientDo(req *http.Request) {
s.Response = &http.Response{
StatusCode: 200,
Body: nil,
}
s.Err = nil
}
func TestGetBankAccounts(t *testing.T) {
sd := new(StubDoer)
cr := new(ClientResponse)
cr.HTTPDoer = HTTPDoer(sd)
accounts, err := cr.GetBankAccounts()
if err != nil {
t.Fatal(err.Errors)
}
t.Log(accounts)
}
=== RUN TestGetBankAccounts
--- FAIL: TestGetBankAccounts (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x12aae69]
There are two common ways to achieve this:
Dependency Injection using interfaces (your example)
Custom http.Transport, which has a hook you can override in your unit tests
It looks like you're close on the interface approach, and are lacking an explicit way to configure the concrete implementation. Consider an interface similiar to your ClientDo:
type HTTPDoer interface {
Do func(req *http.Request) (*http.Response, *RequestError)
}
Dependency injection has the caller configure depedencies and pass them into the resources that actually invoke those dependencies. In this case your ClientResponse struct would have a reference to a HTTPDoer:
type ClientResponse struct {
Response *http.Response
Err *RequestError
HTTPDoer HTTPDoer
}
This allows the caller to configure the concrete implementation that ClientResponse will invoke. In production this will be the actual http.Client but in test it could be anything that implements the Do function.
type StubDoer struct {}
func (s *StubDoer) Do(....)
The unit test could configure the StubDoer, then invoke GetBankAccounts and then make asserstion:
sd := &StubDoer{...}
cr := ClientResponse{
HTTPDoer: sd,
}
accounts, err := cr.GetBankAccounts()
// assertions
The reason it's called Dependency Injection is that the caller initializes the resource (StubDoer) and then provides that resource to the target (ClientResponse). ClientResponse knows nothing about the concrete implementation of HTTPDoer, only that it adheres to the interface!
I wrote a blog post that details dependency injection in the context of unit tests.