I'm interested in creating an httptest.Server which simply records the requests it is called with. To this end, I'd like to use the github.com/matryer/moq library.
To generate a mock for an http.Handler, I copied its definition in a package called client:
package client
import "net/http"
type Handler interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}
I then ran
moq -out handler_mock.go . Handler
inside the client directory, which produced the following handler_mock.go:
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq
package client
import (
"net/http"
"sync"
)
var (
lockHandlerMockServeHTTP sync.RWMutex
)
// Ensure, that HandlerMock does implement Handler.
// If this is not the case, regenerate this file with moq.
var _ Handler = &HandlerMock{}
// HandlerMock is a mock implementation of Handler.
//
// func TestSomethingThatUsesHandler(t *testing.T) {
//
// // make and configure a mocked Handler
// mockedHandler := &HandlerMock{
// ServeHTTPFunc: func(in1 http.ResponseWriter, in2 *http.Request) {
// panic("mock out the ServeHTTP method")
// },
// }
//
// // use mockedHandler in code that requires Handler
// // and then make assertions.
//
// }
type HandlerMock struct {
// ServeHTTPFunc mocks the ServeHTTP method.
ServeHTTPFunc func(in1 http.ResponseWriter, in2 *http.Request)
// calls tracks calls to the methods.
calls struct {
// ServeHTTP holds details about calls to the ServeHTTP method.
ServeHTTP []struct {
// In1 is the in1 argument value.
In1 http.ResponseWriter
// In2 is the in2 argument value.
In2 *http.Request
}
}
}
// ServeHTTP calls ServeHTTPFunc.
func (mock *HandlerMock) ServeHTTP(in1 http.ResponseWriter, in2 *http.Request) {
if mock.ServeHTTPFunc == nil {
panic("HandlerMock.ServeHTTPFunc: method is nil but Handler.ServeHTTP was just called")
}
callInfo := struct {
In1 http.ResponseWriter
In2 *http.Request
}{
In1: in1,
In2: in2,
}
lockHandlerMockServeHTTP.Lock()
mock.calls.ServeHTTP = append(mock.calls.ServeHTTP, callInfo)
lockHandlerMockServeHTTP.Unlock()
mock.ServeHTTPFunc(in1, in2)
}
// ServeHTTPCalls gets all the calls that were made to ServeHTTP.
// Check the length with:
// len(mockedHandler.ServeHTTPCalls())
func (mock *HandlerMock) ServeHTTPCalls() []struct {
In1 http.ResponseWriter
In2 *http.Request
} {
var calls []struct {
In1 http.ResponseWriter
In2 *http.Request
}
lockHandlerMockServeHTTP.RLock()
calls = mock.calls.ServeHTTP
lockHandlerMockServeHTTP.RUnlock()
return calls
}
However, if I try to run the following client_test.go,
package client
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHandlerMock(t *testing.T) {
handlerMock := HandlerMock{
ServeHTTPFunc: func(w http.ResponseWriter, r *http.Request) {},
}
ts := httptest.NewServer(handlerMock)
defer ts.Close()
}
I get
# github.com/kurtpeek/mock-handler/client [github.com/kurtpeek/mock-handler/client.test]
/Users/kurt/go/src/github.com/kurtpeek/mock-handler/client/client_test.go:14:26: cannot use handlerMock (type HandlerMock) as type http.Handler in argument to httptest.NewServer:
HandlerMock does not implement http.Handler (ServeHTTP method has pointer receiver)
FAIL github.com/kurtpeek/mock-handler/client [build failed]
Error: Tests failed.
Indeed if I look at the actual definition of ServeHTTP (from https://github.com/golang/go/blob/c112289ee4141ebc31db50328c355b01278b987b/src/net/http/server.go#L2008-L2013), ServeHTTP() does not have a pointer receiver:
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
How can I fix this to make a test server with a mocked handler function?
The error message is saying that your HandlerMock doesn't implement the interface http.Handler. However, *HandlerMock does implement it:
func (mock *HandlerMock) ServeHTTP(in1 http.ResponseWriter, in2 *http.Request)
Try change the statement ts := httptest.NewServer(handlerMock) to ts := httptest.NewServer(&handlerMock)
Related
I have a interface, and used mockgen to create mocks:
type Client {
getBytes(ctx context.Context, token, url string) ([]byte, error)
SendRequest(ctx context.Context, token, id string) (Response, error)
}
type Response {
...
}
I used mockgen to create the mock file, which looks good. The generated mock file is as this:
// Code generated by MockGen. DO NOT EDIT.
// Source: utils/myservice/client.go
// Package mock_myservice is a generated GoMock package.
package mock_myservice
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
myservice "github.com/robinhoodmarkets/rh/inbox/utils/myservice"
)
// MockClient is a mock of Client interface.
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
}
// MockClientMockRecorder is the mock recorder for MockClient.
type MockClientMockRecorder struct {
mock *MockClient
}
// NewMockClient creates a new mock instance.
func NewMockClient(ctrl *gomock.Controller) *MockClient {
mock := &MockClient{ctrl: ctrl}
mock.recorder = &MockClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClient) EXPECT() *MockClientMockRecorder {
return m.recorder
}
// SendRequest mocks base method.
func (m *MockClient) SendRequest(ctx context.Context, token, id string) (myservice.ToggleSettingsItemResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendRequest", ctx, token, id)
ret0, _ := ret[0].(myservice.ToggleSettingsItemResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SendRequest indicates an expected call of SendRequest.
func (mr *MockClientMockRecorder) SendRequest(ctx, token, id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockClient)(nil).SendRequest), ctx, token, id)
}
// getBytes mocks base method.
func (m *MockClient) **getBytes**(ctx context.Context, token, url string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "getBytes", ctx, token, url)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// getBytes indicates an expected call of getBytes.
func (mr *MockClientMockRecorder) getBytes(ctx, token, url interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getBytes", reflect.TypeOf((*MockClient)(nil).getBytes), ctx, token, url)
}
However, when I tried to used in unittests, i goet this error:
*MockClient does not implement Client (missing getBytes method):
have: mock_myservice.getBytes(context.Context, string, string) ([]byte, error)
want: myservice.getBytes(context.Context, string, string) ([]byte, error)
This is wired since the function is indeed in the mocked file.
Actually I figured out myself:
getBytes is not a public function, as the function names doesn't start with capitalized letter, and hence the implementation is unable to be matched.
This is likely a mockgen issue, as it should give some warning for such usage.
I have a simple Gin server with one of the routes called /metadata.
What the handler does is it reads a file from the system, say /etc/myapp/metadata.json and returns the JSON in the response.
But when the file is not found, handler is configured to return following error.
500: metadata.json does not exists or not readable
On my system, which has the metadata.json file, the test passes. Here is the test function I am using:
package handlers_test
import (
"net/http"
"net/http/httptest"
"testing"
"myapp/routes"
"github.com/stretchr/testify/assert"
)
func TestMetadataRoute(t *testing.T) {
router := routes.SetupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/metadata", nil)
router.ServeHTTP(w, req)
assert.NotNil(t, w.Body)
assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "field1")
assert.Contains(t, w.Body.String(), "field2")
assert.Contains(t, w.Body.String(), "field3")
assert.Contains(t, w.Body.String(), "field4")
}
But on CI environment, the test would fail because it won't find metadata.json. And would return the configured error.
What can be done?
I have this handler:
func GetMetadata(c *gin.Context) {
// read the info
content, err := ioutil.ReadFile("/etc/myapp/metadata.json")
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": "metadata.json does not exists or not readable"})
return
}
// deserialize to json
var metadata models.Metadata
err = json.Unmarshal(content, &metadata)
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": "unable to parse metadata.json"})
return
}
c.JSON(http.StatusOK, metadata)
}
What Volker is suggesting is to use a package-level unexported variable. You give it a fixed default value, corresponding to the path you need in production, and then simply overwrite that variable in your unit test.
handler code:
var metadataFilePath = "/etc/myapp/metadata.json"
func GetMetadata(c *gin.Context) {
// read the info
content, err := ioutil.ReadFile(metadataFilePath)
// ... rest of code
}
test code:
func TestMetadataRoute(t *testing.T) {
metadataFilePath = "testdata/metadata_test.json"
// ... rest of code
}
This is a super-simple solution. There are ways to improve on this, but all are variations of how to inject any variable in a Gin handler. For simple request-scoped configuration, what I usually do is to inject the variable into the Gin context. This requires slightly refactoring some of your code:
router setup code with middleware for production
func SetupRouter() {
r := gin.New()
r.GET("/metadata", MetadataPathMiddleware("/etc/myapp/metadata.json"), GetMetadata)
// ... rest of code
}
func MetadataPathMiddleware(path string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("_mdpath", path)
}
}
handler code extracting the path from context:
func GetMetadata(c *gin.Context) {
metadataFilePath := c.GetString("_mdpath")
content, err := ioutil.ReadFile(metadataFilePath)
// ... rest of code
}
test code which you should refactor to test the handler only (more details: How to unit test a Go Gin handler function?):
func TestMetadataRoute(t *testing.T) {
// create Gin test context
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// inject test value into context
c.Set("_mdpath", "testdata/metadata_test.json")
// just test handler, the passed context holds the test value
GetMetadata(c)
// ... assert
}
Note: setting context values with string keys is somewhat discouraged, however the Gin context accepts only string keys.
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)
// ...
}
Given:
// FileReader interface for reading from a file
type FileReader interface {
ReadFile(filename string) ([]byte, error)
}
type FileRead struct {}
// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
return ioutil.ReadFile(fn)
}
type Dev struct {
*FileRead
}
func NewDev() *Dev {
frd := FileRead{}
return &Dev{frd}
}
// Function that does some job
func (dev Dev) DoSomeStuff() {
//...
dev.ReadFile("file")
//...
}
func main () {
doer := NewDev()
doer.DoSomeStuff()
}
During unit testing, the ReadFile operation should be mocked. How can one best achieve this in go test?
Dev struct could instead use FileReader interface, but then struct embedding is no longer used and the syntax in DoSomeStuff becomes less obvious.
If you are using a DI framework such as Dargo you can inject something that implements FileReader into dev. In your main-line code you would bind the normal FileReader, but in your test you can use a mock FileReader. The rest of your code should not know the difference. It would look something like this:
import (
"github.com/jwells131313/dargo/ioc"
"io/ioutil"
"testing"
)
type FileReader interface {
ReadFile(filename string) ([]byte, error)
}
type FileRead struct{}
// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
return ioutil.ReadFile(fn)
}
type Dev struct {
MyReader FileReader `inject:"FileReader"`
}
/* Not here anymore, but you can implement DargoInitializer
if you need more initialization of Dev
func NewDev() *Dev {
frd := FileRead{}
return &Dev{frd}
}
*/
// Function that does some job
func (dev Dev) DoSomeStuff() {
//...
dev.MyReader.ReadFile("file")
//...
}
var locator ioc.ServiceLocator
func initialize() error {
l, err := ioc.CreateAndBind("Example", func(binder ioc.Binder) error {
binder.Bind("Dev", &Dev{})
binder.Bind("FileReader", &FileRead{})
return nil
})
locator = l
return err
}
func main() {
initialize()
raw, _ := locator.GetDService("Dev")
doer := raw.(*Dev)
doer.DoSomeStuff()
}
// Here is test code
type TestFileRead struct{}
// ReadFile is a mock that just returns a zero-length byte array
func (tfr TestFileRead) ReadFile(fn string) ([]byte, error) {
return []byte{}, nil
}
func TestMe(t *testing.T) {
initialize()
ioc.BindIntoLocator(locator, func(binder ioc.Binder) error {
binder.Bind("FileReader", &TestFileRead{}).Ranked(1)
return nil
})
// Test Me!
}
In the above example the "normal" file reader is injected in the normal code, but in the test there is a TestFileReader with a higher rank. So when you went to get Dev in the test it would be injected with the test one, not the one from the main-line code.
Hope this helps.
I'm writing a REST API using Gin framework. But I was faced a trouble testing my controllers and researching TDD and Mock. I tried to apply TDD and Mock to my code but I could not.
I created a very reduced test environment and tried to create a controller test. How do I create a Mock for Gin.Context?
Here's my example code:
package main
import (
"strconv"
"github.com/gin-gonic/gin"
)
// MODELS
type Users []User
type User struct {
Name string `json"name"`
}
func main() {
r := gin.Default()
r.GET("/users", GetUsers)
r.GET("/users/:id", GetUser)
r.Run(":8080")
}
// ROUTES
func GetUsers(c *gin.Context) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
}
func GetUser(c *gin.Context) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.Get(c, repo)
}
// CONTROLLER
type UserController struct{}
func (ctrl UserController) GetAll(c *gin.Context, repository UserRepositoryIterface) {
c.JSON(200, repository.GetAll())
}
func (ctrl UserController) Get(c *gin.Context, repository UserRepositoryIterface) {
id := c.Param("id")
idConv, _ := strconv.Atoi(id)
c.JSON(200, repository.Get(idConv))
}
// REPOSITORY
type UserRepository struct{}
type UserRepositoryIterface interface {
GetAll() Users
Get(id int) User
}
func (r UserRepository) GetAll() Users {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users
}
func (r UserRepository) Get(id int) User {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users[id-1]
}
My test example:
package main
import(
"testing"
_ "github.com/gin-gonic/gin"
)
type UserRepositoryMock struct{}
func (r UserRepositoryMock) GetAll() Users {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users
}
func (r UserRepositoryMock) Get(id int) User {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users[id-1]
}
// TESTING REPOSITORY FUNCTIONS
func TestRepoGetAll(t *testing.T) {
userRepo := UserRepository{}
amountUsers := len(userRepo.GetAll())
if amountUsers != 2 {
t.Errorf("Esperado %d, recebido %d", 2, amountUsers)
}
}
func TestRepoGet(t *testing.T) {
expectedUser := struct{
Name string
}{
"Wilson",
}
userRepo := UserRepository{}
user := userRepo.Get(1)
if user.Name != expectedUser.Name {
t.Errorf("Esperado %s, recebido %s", expectedUser.Name, user.Name)
}
}
/* HOW TO TEST CONTROLLER?
func TestControllerGetAll(t *testing.T) {
gin.SetMode(gin.TestMode)
c := &gin.Context{}
c.Status(200)
repo := UserRepositoryMock{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
}
*/
Gin provides the option to create a Test Context which you can use for whatever you need:
https://godoc.org/github.com/gin-gonic/gin#CreateTestContext
Like that:
c, _ := gin.CreateTestContext(httptest.NewRecorder())
Here is an example of how I mock a context, add a param, use it in a function, then print the string of the response if there was a non-200 response.
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = []gin.Param{gin.Param{Key: "k", Value: "v"}}
foo(c)
if w.Code != 200 {
b, _ := ioutil.ReadAll(w.Body)
t.Error(w.Code, string(b))
}
In order to get a *gin.Context instance that you can test, you need a mock HTTP request and response. An easy way to create those is to use the net/http and net/http/httptest packages. Based on the code you linked, your test would look like this:
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestControllerGetAll(t *testing.T) {
// Switch to test mode so you don't get such noisy output
gin.SetMode(gin.TestMode)
// Setup your router, just like you did in your main function, and
// register your routes
r := gin.Default()
r.GET("/users", GetUsers)
// Create the mock request you'd like to test. Make sure the second argument
// here is the same as one of the routes you defined in the router setup
// block!
req, err := http.NewRequest(http.MethodGet, "/users", nil)
if err != nil {
t.Fatalf("Couldn't create request: %v\n", err)
}
// Create a response recorder so you can inspect the response
w := httptest.NewRecorder()
// Perform the request
r.ServeHTTP(w, req)
// Check to see if the response was what you expected
if w.Code != http.StatusOK {
t.Fatalf("Expected to get status %d but instead got %d\n", http.StatusOK, w.Code)
}
}
Although you could create a mock *gin.Context, it's probably easier to use the method above, since it'll execute and handle your request the same as it would an actual request.
If to reduce the question to "How to create mock for a function argument?" the answer is: use interfaces not concrete types.
type Context struct is a concrete type literal and Gin doesn't provide appropriate interface. But you can declare it by yourself. Since you are using only JSON method from Context you can declare extra-simple interface:
type JSONer interface {
JSON(code int, obj interface{})
}
And use JSONer type instead Context type in all your functions which expect Context as argument:
/* Note, you can't declare argument as a pointer to interface type,
but when you call it you can pass pointer to type which
implements the interface.*/
func GetUsers(c JSONer) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
}
func GetUser(c JSONer) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.Get(c, repo)
}
func (ctrl UserController) GetAll(c JSONer, repository UserRepositoryIterface) {
c.JSON(200, repository.GetAll())
}
func (ctrl UserController) Get(c JSONer, repository UserRepositoryIterface) {
id := c.Param("id")
idConv, _ := strconv.Atoi(id)
c.JSON(200, repository.Get(idConv))
}
And now it is easy to test
type ContextMock struct {
JSONCalled bool
}
func (c *ContextMock) JSON(code int, obj interface{}){
c.JSONCalled = true
}
func TestControllerGetAll(t *testing.T) {
gin.SetMode(gin.TestMode)
c := &ContextMock{false}
c.Status(200)
repo := UserRepositoryMock{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
if c.JSONCalled == false {
t.Fail()
}
}
Example simple as possible.
There is another question with a close sense