I am trying to use Mockery to mock a Http client, but when I try to pass in the return value I get an error saying type http.Response is not an expression
Below is my interface and handler
type HTTPClient interface {
Get(url string) (resp *http.Response, err error)
}
type Handler struct {
httpClient HTTPClient
}
Mockery generated a mock class which looks like the below
type HTTPClient struct {
mock.Mock
}
// Get provides a mock function with given fields: url
func (_m *HTTPClient) Get(url string) (*http.Response, error) {
ret := _m.Called(url)
var r0 *http.Response
if rf, ok := ret.Get(0).(func(string) *http.Response); ok {
r0 = rf(url)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*http.Response)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(url)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
In my test, I am trying to mock the httpclient like below
httpClient := &mocks.HTTPClient{}
httpClient.On("Get", request.SubscribeURL).Return(resp*http.Response, nil)
I am not sure what parameters I am supposed to in Return. What should I be passing?
The http.Response (notice it is without square brackets, it denotes a type mostly) is an expression, while you should return actual response (a return value of the function), since a function returns two values *http.Response a pointer to http response struct and an error or nil.
body := ioutil.NopCloser(strings.NewReader("hello world"))
httpRespose := &http.Response{Body: body, Status: "200 OK", StatusCode: 200}
httpClient.On("Get", request.SubscribeURL)
.Return(
httpRespose, // http response with string in the body and the code 200
nil, // no error occured while response processed
)
Related
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
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{}
I have a controller function like this....
func GetMaterialByFilter(c *gin.Context) {
queryParam := weldprogs.QueryParam{}
c.BindQuery(&queryParam)
materialByFilter, getErr := services.WeldprogService.GetMaterialByFilter(&queryParam)
if getErr != nil {
//TODO : Handle user creation error
c.JSON(getErr.Status, getErr)
return
}
c.JSON(http.StatusOK, materialByFilter)
}
QueryParam Struct is like this..
type QueryParam struct {
Basematgroup_id []string `form:"basematgroup_id"`
License_id []string `form:"license_id"`
Diameter_id []string `form:"diameter_id"`
Gasgroup_id []string `form:"gasgroup_id"`
Wiregroup_id []string `form:"wiregroup_id"`
Wiremat_id []string `form:"wiremat_id"`
}
My test function is like this..
func TestGetMaterialByFilter(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
GetMaterialByFilter(c)
assert.Equal(t, 200, w.Code)
var got gin.H
err := json.Unmarshal(w.Body.Bytes(), &got)
if err != nil {
t.Fatal(err)
}
fmt.Println(got)
assert.Equal(t, got, got)
}
On running this test it is giving me the following error
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=0x10 pc=0x97f626]
But when i comment out the c.BindQuery() line in my controller function it successfully run my test function. What i am doing wrong here? can i somehow mock the c.BindQuery function?
To test operations that involve the HTTP request, you have to actually initialize an *http.Request and set it to the Gin context. To specifically test c.BindQuery it's enough to properly initialize the request's URL and URL.RawQuery:
func mockGin() (*gin.Context, *httptest.ResponseRecorder) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// test request, must instantiate a request first
req := &http.Request{
URL: &url.URL{},
Header: make(http.Header), // if you need to test headers
}
// example: req.Header.Add("Accept", "application/json")
// request query
testQuery := weldprogs.QueryParam{/* init fields */}
q := req.URL.Query()
for _, s := range testQuery.Basematgroup_id {
q.Add("basematgroup_id", s)
}
// ... repeat for other fields as needed
// must set this, since under the hood c.BindQuery calls
// `req.URL.Query()`, which calls `ParseQuery(u.RawQuery)`
req.URL.RawQuery = q.Encode()
// finally set the request to the gin context
c.Request = req
return c, w
}
If you need to mock JSON binding, see this answer.
The service call services.WeldprogService.GetMaterialByFilter(&queryParam) can't be tested as is. To be testable it has to be (ideally) an interface and somehow injected as dependency of your handler.
Assuming that it is already an interface, to make it injectable, you either require it as an handler argument — but this forces you to change the signature of the handler —, or you set it as a Gin context value:
func GetMaterialByFilter(c *gin.Context) {
//...
weldprogService := mustGetService(c)
materialByFilter, getErr := weldprogService.GetMaterialByFilter(&queryParam)
// ...
}
func mustGetService(c *gin.Context) services.WeldprogService {
svc, exists := c.Get("svc_context_key")
if !exists {
panic("service was not set")
}
return svc.(services.WeldprogService)
}
Then you can mock it in your unit tests:
type mockSvc struct {
}
// have 'mockSvc' implement the interface
func TestGetMaterialByFilter(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// now you can set mockSvc into the test context
c.Set("svc_context_key", &mockSvc{})
GetMaterialByFilter(c)
// ...
}
I have a controller function like this....
func GetMaterialByFilter(c *gin.Context) {
queryParam := weldprogs.QueryParam{}
c.BindQuery(&queryParam)
materialByFilter, getErr := services.WeldprogService.GetMaterialByFilter(&queryParam)
if getErr != nil {
//TODO : Handle user creation error
c.JSON(getErr.Status, getErr)
return
}
c.JSON(http.StatusOK, materialByFilter)
}
QueryParam Struct is like this..
type QueryParam struct {
Basematgroup_id []string `form:"basematgroup_id"`
License_id []string `form:"license_id"`
Diameter_id []string `form:"diameter_id"`
Gasgroup_id []string `form:"gasgroup_id"`
Wiregroup_id []string `form:"wiregroup_id"`
Wiremat_id []string `form:"wiremat_id"`
}
My test function is like this..
func TestGetMaterialByFilter(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
GetMaterialByFilter(c)
assert.Equal(t, 200, w.Code)
var got gin.H
err := json.Unmarshal(w.Body.Bytes(), &got)
if err != nil {
t.Fatal(err)
}
fmt.Println(got)
assert.Equal(t, got, got)
}
On running this test it is giving me the following error
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=0x10 pc=0x97f626]
But when i comment out the c.BindQuery() line in my controller function it successfully run my test function. What i am doing wrong here? can i somehow mock the c.BindQuery function?
To test operations that involve the HTTP request, you have to actually initialize an *http.Request and set it to the Gin context. To specifically test c.BindQuery it's enough to properly initialize the request's URL and URL.RawQuery:
func mockGin() (*gin.Context, *httptest.ResponseRecorder) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// test request, must instantiate a request first
req := &http.Request{
URL: &url.URL{},
Header: make(http.Header), // if you need to test headers
}
// example: req.Header.Add("Accept", "application/json")
// request query
testQuery := weldprogs.QueryParam{/* init fields */}
q := req.URL.Query()
for _, s := range testQuery.Basematgroup_id {
q.Add("basematgroup_id", s)
}
// ... repeat for other fields as needed
// must set this, since under the hood c.BindQuery calls
// `req.URL.Query()`, which calls `ParseQuery(u.RawQuery)`
req.URL.RawQuery = q.Encode()
// finally set the request to the gin context
c.Request = req
return c, w
}
If you need to mock JSON binding, see this answer.
The service call services.WeldprogService.GetMaterialByFilter(&queryParam) can't be tested as is. To be testable it has to be (ideally) an interface and somehow injected as dependency of your handler.
Assuming that it is already an interface, to make it injectable, you either require it as an handler argument — but this forces you to change the signature of the handler —, or you set it as a Gin context value:
func GetMaterialByFilter(c *gin.Context) {
//...
weldprogService := mustGetService(c)
materialByFilter, getErr := weldprogService.GetMaterialByFilter(&queryParam)
// ...
}
func mustGetService(c *gin.Context) services.WeldprogService {
svc, exists := c.Get("svc_context_key")
if !exists {
panic("service was not set")
}
return svc.(services.WeldprogService)
}
Then you can mock it in your unit tests:
type mockSvc struct {
}
// have 'mockSvc' implement the interface
func TestGetMaterialByFilter(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// now you can set mockSvc into the test context
c.Set("svc_context_key", &mockSvc{})
GetMaterialByFilter(c)
// ...
}
I have a job as a unit-tester, and there's a couple of functions that, as they are, are untestable. I have tried telling my immediate boss this, and he's telling me that I cannot refactor the code to make it testable. I will bring it up in today's meeting, but first, I want to make sure that I have a solid plan on doing the refactoring such that the business use case doesn't change.
The method
The method itself is defined like this:
//SendRequest This is used to contact the apiserver synchronously.
func (apiPath *APIPath) SendRequest(context interface{}, tokenHandler *apiToken.APITokenHandlerSt,
header map[string]string,
urlParams []string, urlQueries url.Values,
jsonBody []byte) apiCore.CallResultSt {
if apiToken := tokenHandler.GetToken(apiPath.AuthType, apiPath.Scope); apiToken != nil {
return apiPath.APICoreHandler.SendRequest(
context,
apiToken.Token,
apiPath.GetPath(urlParams, urlQueries), apiPath.Type,
header, jsonBody)
} else {
errMsg, _ := json.Marshal(errors.InvalidAuthentication())
return apiCore.CallResultSt{DetailObject: errMsg, IsSucceeded: false}
}
}
where its receiver object is defined thus:
//APIPath=======================
//Used for url construction
type APIPath struct {
APICoreHandler *apiCore.APICoreSt
// domain name of API
DomainPath string
ParentAPI *APIPath
Type apiCore.APIType
// subfunction name
SubFunc string
KeyName string
AutoAddKeyToPath bool
AuthType oAuth2.OAuth2Type
Scope string
}
Dependencies
You may have observed at least two of them: tokenHandler.GetToken and APICoreHandler.SendRequest
The definitions of those, and their objects are as follows:
tokenHandler
type APITokenHandlerSt struct {
Tokens []APITokenSt
}
tokenHandler.GetToken
// GetToken returns the token having the specified `tokenType` and `scope`
//
// Parameters:
// - `tokenType`
// - `scope`
//
// Returns:
// - pointer to Token having `tokenType`,`scope` or nil
func (ath *APITokenHandlerSt) GetToken(tokenType oAuth2.OAuth2Type, scope string) *APITokenSt {
if ath == nil {
return nil
}
if i := ath.FindToken(tokenType, scope); i == -1 {
return nil
} else {
return &ath.Tokens[i]
}
}
APICoreHandler
type APICoreSt struct {
BaseURL string
}
APICoreHandler.SendRequest
//Establish the request to send to the server
func (a *APICoreSt) SendRequest(context interface{}, token string, apiURL string, callType APIType, header map[string]string, jsonBody []byte) CallResultSt {
if header == nil {
header = make(map[string]string)
}
if header["Authorization"] == "" {
header["Authorization"] = "Bearer " + token
}
header["Scope"] = GeneralScope
header["Content-Type"] = "application/json; charset=UTF-8"
return a.CallServer(context, callType, apiURL, header, jsonBody)
}
APICoreHandler.CallServer
//CallServer Calls the server
//
// Parameters:
// - `context` : a context to pass to the server
// - `apiType` : the HTTP method (`GET`,`POST`,`PUT`,`DELETE`,...)
// - `apiURL` : the URL to hit
// - `header` : request header
// - `jsonBody`: the JSON body to send
//
// Returns:
// - a CallResultSt. This CallResultSt might have an error for its `DetailObject`
func (a *APICoreSt) CallServer(context interface{}, apiType APIType, apiURL string, header map[string]string, jsonBody []byte) CallResultSt {
var (
Url = a.BaseURL + apiURL
err error
res *http.Response
resBody json.RawMessage
hc = &http.Client{}
req = new(http.Request)
)
req, err = http.NewRequest(string(apiType), Url, bytes.NewBuffer(jsonBody))
if err != nil {
//Use a map instead of errorSt so that it doesn't create a heavy dependency.
errorSt := map[string]string{
"Code": "ez020300007",
"Message": "The request failed to be created.",
}
logger.Instance.LogError(err.Error())
err, _ := json.Marshal(errorSt)
return CallResultSt{DetailObject: err, IsSucceeded: false}
}
for k, v := range header {
req.Header.Set(k, v)
}
res, err = hc.Do(req)
if res != nil {
resBody, err = ioutil.ReadAll(res.Body)
res.Body = ioutil.NopCloser(bytes.NewBuffer(resBody))
}
return CallResultSt{resBody, logger.Instance.CheckAndHandleErr(context, res)}
}
My progress thus far
Obviously, tokenHandler has no business being passed in as an object, especially when its state is not being used. Thus, making that testable would be as simple as create a one-method interface, and use it instead of the *apiToken.APITokenHandlerSt
My concern, however, is with that APICoreHandler and its SendRequest method. I would like to know how to refactor it such that the use case of this code under test doesn't change, whilst allowing me to control this.
This is imperative, because most of the methods I have yet to test, hit apiPath.SendRequest somehow
UPDATE: I made the following test attempt, which caused panic:
func TestAPIPath_SendRequest(t *testing.T) {
// create a fake server that returns a string
fakeServer := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world!")
}))
defer fakeServer.Close()
// define some values
scope := "testing"
authType := oAuth2.AtPassword
// create a tokenHandler
tokenHandler := new(apiToken.APITokenHandlerSt)
tokenHandler.Tokens = []apiToken.APITokenSt{
apiToken.APITokenSt{
Scope: scope,
TokenType: authType,
Token: "dummyToken",
},
}
// create some APIPaths
validAPIPath := &APIPath{
Scope: scope,
AuthType: authType,
}
type args struct {
context interface{}
tokenHandler *apiToken.APITokenHandlerSt
header map[string]string
urlParams []string
urlQueries url.Values
jsonBody []byte
}
tests := []struct {
name string
apiPath *APIPath
args args
want apiCore.CallResultSt
}{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.apiPath.SendRequest(tt.args.context, tt.args.tokenHandler, tt.args.header, tt.args.urlParams, tt.args.urlQueries, tt.args.jsonBody); !reflect.DeepEqual(got, tt.want) {
t.Errorf("APIPath.SendRequest() = %v, want %v", got, tt.want)
}
})
}
t.Run("SanityTest", func(t *testing.T) {
res := validAPIPath.SendRequest("context",
tokenHandler,
map[string]string{},
[]string{},
url.Values{},
[]byte{},
)
assert.True(t,
res.IsSucceeded)
})
}