I am trying to do unit testing, I am using GraphQL gqlgen for this case with Echo, I have been struggle to do test with my context, I can't add header in my testing due the GraphQL not using the server endpoint
here is my code to set the context value
var ctx *http.Request
var echo echo.Context
h := context.WithValue(ctx.Context(), "Token", token)
tk := ctx.WithContext(h)
echo.SetRequest(tk)
I do this on middleware function when I run my server, nothing issue,
here is how I call that in my resolver
// c is context.Context
tokenHeader = c.Value("Token")
but in testing I create that codes
always got:
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=0x1703953]
goroutine 30 [running]:
testing.tRunner.func1.1(0x17b3000, 0x1f47810)
/usr/local/Cellar/go/1.14.3/libexec/src/testing/testing.go:940 +0x2f5
testing.tRunner.func1(0xc000378900)
/usr/local/Cellar/go/1.14.3/libexec/src/testing/testing.go:943 +0x3f9
panic(0x17b3000, 0x1f47810)
/usr/local/Cellar/go/1.14.3/libexec/src/runtime/panic.go:969 +0x166
spur/test.TestCredential.func3(0xc000378900)
/Users/me/Documents/project/test/main_test.go:144 +0x193
testing.tRunner(0xc000378900, 0xc00034fdf0)
/usr/local/Cellar/go/1.14.3/libexec/src/testing/testing.go:991 +0xdc
created by testing.(*T).Run
/usr/local/Cellar/go/1.14.3/libexec/src/testing/testing.go:1042 +0x357
is it possible to set the context value during in test?
here is may full testing code:
func TestCredential(t *testing.T) {
config := resolver.Config{
Resolvers: resolver.New(),
}
clientTest := testClient.New(handler.NewDefaultServer(resolver.NewExecutableSchema(config)))
t.Run("Should Success Change Password", func(t *testing.T) {
var ctx *http.Request
var echo echo.Context
h := context.WithValue(ctx.Context(), "Token", token)
tk := ctx.WithContext(h)
echo.SetRequest(tk)
var respond map[string]interface{}
mutation := `mutation {
changeCredentialConfirmation(
currentPassword: ""
newChange: "newPassword"
isForgotPassword: true
isChangePassword: false
)
}`
clientTest.MustPost(
mutation,
&respond,
)
require.True(t, respond["changeCredentialConfirmation"].(bool), "Check your email")
})
}
Related
I'm using gin-gonic for a server, and testify for testing and mocks, along with "testing" and "net/http/httptest"
The part of the interface that mocks the method:
func (m *MockInterface) Method(ctx context.Context, id string, deleted bool) ([]models.Entity, error) {
args := m.Called(ctx, id, deleted)
var entities []models.Entity
if args.Get(0) != nil {
entities = args.Get(0).([]models.Entity)
}
var err error
if args.Get(1) != nil {
err = args.Error(1)
}
return entities, err
}
Setting it up in a test - the server is setup outside of this t.Run, there are tests before this that run fine.
t.Run("TestName", func(t *testing.T) {
mockInterface := new(mocks.MockInterface)
mockInterface.On("Method", mock.AnythingOfType("*context.timerCtx"), id.String(), true).Return(mockResp, nil)
// a response writer to capture the response
rr := httptest.NewRecorder()
url := "SomeURLString"
// make the request to the Method handler
request, err := http.NewRequest(http.MethodGet, url, nil)
assert.NoError(t, err)
router.ServeHTTP(rr, request)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rr.Code)
mockInterface.AssertExpectations(t)
})
This is where it panics:
router.ServeHTTP(rr, request)
mock: Unexpected Method Call
-----------------------------
Method(*context.timerCtx,string,bool)
0: &context.timerCtx{cancelCtx:context.cancelCtx{Context:(*context.emptyCtx)...etc}}
1: "MatchingID"
2: true
The closest call I have is:
Method(mock.AnythingOfTypeArgument,string,bool)
0: "*context.timerCtx"
1: "MatchingID"
2: false
When I go into debug mode, mockInterface.Mock.ExpectedCalls[0].Arguments[2] is true, just as I set it. And then it panics and says it's not... while it is still true!
I've gone far enough into the stack to verify that the handler called method with the boolean as true, so it ought to pass. Instead it panics, and I'm not sure where to go from here to figure out why.
Does anyone know what is going on here? Is there some kind of odd interaction between gin and testify that I'm missing? Thank you.
I want to unit test gin controller function.
package controllers
import (
"fmt"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/go-playground/assert/v2"
)
func TestGetDivisions(t *testing.T) {
tt := []struct {
Name string
EnterpriseID string
Code int
}{
{Name: "Enterprise id exists", EnterpriseID: "1", Code: 200},
{Name: "Enterprise id cannot be string", EnterpriseID: "string", Code: 404},
}
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
for _, tc := range tt {
c.Params = []gin.Param{
{
Key: "enterprise_id",
Value: tc.EnterpriseID,
},
}
GetDivisions(c) // Error from here
assert.Equal(t, tc.Code, w.Code, tc.Name)
}
}
Problem is, whenever I use GetDivision function inside a test file gives error like below:
--- FAIL: TestGetDivisions (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=0x28 pc=0x43c7438]
Cannot find where is a problem. Can I give test context to a function as a parameter?
And here is my GetDivisions handler:
// Get divisions of an enterprise by given id. Default id = "1"
func GetDivisions(c *gin.Context) {
var divisions []models.Division
if err := config.DB.Find(&divisions).Error; err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, gin.H{
"divisions": divisions,
})
}
It seems like an error occurs in config.DB.Find(&divisions), because I think invalid memory error accurs when it reads from database and writes to variable... can you show the code of config.DB.Find(). (if it is on GitHub, you can send me link)
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'm setting up testing in Go.
I use go-sqlmock to test mysql connection and Go Gin as framework. Now I try to test mysql insert logic.
The problem is I need to set mock gin.Context which is used for BindJSON later.
But I can't set this gin.Context so far.
server side: golang
db: mysql
web framework: gin
dao.go
unc PostImageToDBDao(c *gin.Context, db *sql.DB) {
// Because of this logic, I need to set gin.Context with json
var imageData util.ImageData
c.BindJSON(&imageData)
for _, imageName := range imageData.IMAGENAMES {
ins, err := db.Prepare("INSERT INTO images(article_uuid, image_name) VALUES(?,?)")
if err != nil {
log.Fatal(err)
}
ins.Exec(imageData.ARTICLEUUID, imageName.NAME)
}
}
dao_test.go
func TestPostImageToDBDao(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
prep := mock.ExpectPrepare("^INSERT INTO images*")
prep.ExpectExec().
WithArgs("bea1b24d-0627-4ea0-aa2b-8af4c6c2a41c", "b8119536-fad5-4ffa-ab71-2f96cca19697").
WillReturnResult(sqlmock.NewResult(1, 1))
// try to set context with json post
req, _ := http.NewRequest("POST", "/post/image/db", bytes.NewBuffer([]byte(`[{
"articleUUID": "bea1b24d-0627-4ea0-aa2b-8af4c6c2a41c",
"imageNames": "b8119536-fad5-4ffa-ab71-2f96cca19697",
}]`)))
req.Header.Set("Content-Type", "application/json")
var context *gin.Context
context = &gin.Context{Request: req}
PostImageToDBDao(context, db)
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expections: %s", err)
}
}
I expect mock gin.Context to set properly and run go test -v without error, however it fails with the following error:
=== RUN TestPostImageToDBDao
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 0 with 400
--- FAIL: TestPostImageToDBDao (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=0x60 pc=0x15bde75]
goroutine 50 [running]:
testing.tRunner.func1(0xc000234100)
/usr/local/go/src/testing/testing.go:830 +0x392
panic(0x16918e0, 0x1ce5850)
/usr/local/go/src/runtime/panic.go:522 +0x1b5
github.com/gin-gonic/gin.(*Context).AbortWithStatus(0xc00026aee8, 0x190)
/Users/jpskgc/go/pkg/mod/github.com/gin-gonic/gin#v1.4.0/context.go:146 +0x45
github.com/gin-gonic/gin.(*Context).AbortWithError(0xc0000d9ee8, 0x190, 0x1863e00, 0xc0002700a0, 0x1863e00)
/Users/jpskgc/go/pkg/mod/github.com/gin-gonic/gin#v1.4.0/context.go:162 +0x39
github.com/gin-gonic/gin.(*Context).MustBindWith(0xc00026aee8, 0x16328e0, 0xc00022c180, 0x186e060, 0x1d16588, 0x1e316d0, 0x0)
/Users/jpskgc/go/pkg/mod/github.com/gin-gonic/gin#v1.4.0/context.go:561 +0x92
github.com/gin-gonic/gin.(*Context).BindJSON(...)
/Users/jpskgc/go/pkg/mod/github.com/gin-gonic/gin#v1.4.0/context.go:528
article/api/dao.PostImageToDBDao(0xc00026aee8, 0xc000276000)
/Users/jpskgc/article/api/dao/dao.go:54 +0x87
article/api/dao.TestPostImageToDBDao(0xc000234100)
/Users/jpskgc/article/api/dao/dao_test.go:204 +0x4b6
testing.tRunner(0xc000234100, 0x17897e0)
/usr/local/go/src/testing/testing.go:865 +0xc0
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:916 +0x35a
exit status 2
FAIL article/api/dao 0.032s
First, you must instantiate a test *gin.Context and make sure its *http.Request is non-nil:
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
Then you can mock a POST json body in the following way:
func MockJsonPost(c *gin.Context /* the test context */, content interface{}) {
c.Request.Method = "POST" // or PUT
c.Request.Header.Set("Content-Type", "application/json")
jsonbytes, err := json.Marshal(content)
if err != nil {
panic(err)
}
// the request body must be an io.ReadCloser
// the bytes buffer though doesn't implement io.Closer,
// so you wrap it in a no-op closer
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonbytes))
}
where the function argument content interface{} is anything that can be marshalled into JSON with json.Marshal(), so in most cases a struct with the proper json tags, or a map[string]interface{}.
Example usage:
func TestMyHandler(t *testing.T) {
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
ctx.Request = &http.Request{
Header: make(http.Header),
}
MockJsonPost(ctx, map[string]interface{}{"foo": "bar"})
MyHandler(ctx)
assert.EqualValues(t, http.StatusOK, w.Code)
}
Related:
How to unit test a Go Gin handler function?