I'm working on my first Go + Gin + GORM project. I've two models: List and Todo as follows:
type List struct {
gorm.Model
Name string
Todos []*Todo `gorm:"many2many:list_todos;"`
}
type Todo struct {
gorm.Model
Title string
Lists []*List `gorm:"many2many:list_todos;"`
}
I've a helper function to add a Todo to a List:
func (self *List) AddTodos(db *gorm.DB, todos ...*Todo) error {
return db.Model(self).Association("Todos").Append(todos)
}
I want to unit test the AddTodos function. I'm using sqlmock for mocking. This is the code for the unit test:
func TestAddTodosWithNoTodos(t *testing.T) {
gormDB, db, mock, _ := misc.GetMockedGormDBWithMock()
defer db.Close()
mockList := &List{
Model: gorm.Model{ID: 1},
}
mock.NewRows([]string{"id"}).AddRow(mockList.ID)
if err := mockList.AddTodos(gormDB); err != nil {
// test fails here
t.Errorf("Expected no error, got %v", err)
}
todos := []Todo{}
if err := gormDB.Model(mockList).Association("Todos").Find(&todos); err != nil {
// test also fails here
t.Errorf("Expected no error, got %v", err)
}
if len(todos) != 0 {
t.Errorf("Expected no todos, got %v", todos)
}
}
I've marked the two places where the test fails. The error object being checked at both these places are from GORM, however, in the test failure error messages, it is revealed that sqlmock is causing these errors:
First error message:
Expected no error, got all expectations were already fulfilled, call to database transaction Begin was not expected
Second error message:
Expected no error, got all expectations were already fulfilled
I don't want to test which queries are executed, hence, I've not used sqlmock's ExpectQuery. Still, "unexpected queries" are causing errors.
How can I unit test AddTodos without testing the queries it performs?
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.
A few days ago, I started learning Golang, PostgreSQL, and Unit tests.
I searched a lot of information about how to API unit test in GORM here and there.
I just tried to follow up Golang documentation (https://go.dev/src/net/http/httptest/example_test.go) and one of the StackOverflow posts (https://codeburst.io/unit-testing-for-rest-apis-in-go-86c70dada52d)
I am not sure I got this error message "undefined: RegisterTodoListRoutes".
handler := http.HandlerFunc(RegisterTodoListRoutes) in this line
My todolist-routes.go code is
package routes
import (
"github.com/gorilla/mux"
"github.com/jiwanjeon/go-todolist/pkg/controllers"
)
var RegisterTodoListRoutes = func (router *mux.Router){
router.HandleFunc("/todo/", controllers.CreateTodo).Methods("POST")
router.HandleFunc("/todo/", controllers.GetTodo).Methods("GET")
router.HandleFunc("/todo/{todoId}", controllers.GetTodoById).Methods("GET")
router.HandleFunc("/todo/{todoId}", controllers.UpdateTodo).Methods("PUT")
router.HandleFunc("/todo/{todoId}", controllers.DeleteTodo).Methods("DELETE")
router.HandleFunc("/complete/{todoId}", controllers.CompleteTodo).Methods("PUT")
router.HandleFunc("/incomplete/{todoId}", controllers.InCompleteTodo).Methods("PUT")
}
and my todolist-routes_test.go test code is
package routes_test
import (
"net/http/httptest"
"testing"
"net/http"
)
func TestRegisterTodoListRoutes(t *testing.T) {
req, err := http.NewRequest("GET", "/todo/", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(RegisterTodoListRoutes)
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
expected := `[{"ID":46,"title":"test-title-console-check","last_name":"test-description-console-check", "conditions": true]`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}
Here is a link for todolist_routes.go : https://go.dev/play/p/XoVMbK7PnLu
this is for test code : https://go.dev/play/p/NddwrU5m6ss
I really appreciate your help!!
Edit 1] I found the reason why I got this error. When I remove _test above at package and run it. I got this error "cannot convert Routes (type func(*mux.Router)) to type http.HandlerFunc"
I have the following function that makes multiple calls to AWS IAM. I am able to run unit tests on single calls. However when I run a test on the one below I get a panic: "runtime error, invalid memory or nil pointer dereference"
func (iamDependency *iamService) CreateMyUser(userName string) (string, error){
//first IAM call
err:=iamDependency.GetUser(&iam.GetUserInput{UserName: userName})
if err != nil {
fmt.Println("Failed to get user, attempting to create")
//second IAM call
err:=iamDependency.CreateUser(&iam.CreateUserInput{UserName: userName})
if err != nil {
log.Fatalf("Failed to create user\n", err )
}
}
}
Here is my mock and test:
type mockSomeOutput{}
type mockedIamCall{
iamiface.IAMAPI
Response mockSomeOutput
}
func TestCreateMyUser(t *testing.T){
t.Run("Successful user create", fun(t *testing.T){
mo:= mockOutput{}
m:= mockedIamCall{Response: mo}
d:= iamService{
iamInstance: m,
}
mockedUser:="TestUser"
_, err:= d.ResetCredentials(&mockedUser)
if err != nil {
t.Fatal("Everything should be ok")
}
})
}
I'm wondering whether there are any tricks or guidelines for making unit tests for this kind of function in Golang.
Appreciate any help.
You probably want to try using: https://github.com/golang/mock
You can creating mock implementation for the iamiface.IAMAPI (from the actual interface) then expecting the function calls and mocking the response.
Creating the mock implementation of the interface using mockgen.
mockgen -source={path to IAM API interface}
And then you can expect the function calls with something like this on the test cases:
function TestExample(t *testing.T) {
ctrl := gomock.NewController(t)
mockIAMAPI := mock_package.NewMockIAMAPI(ctrl)
mockIAMAPI.EXPECT().GetUser(expectedInput).Return(mockResponse).Times(1)
}
#Raymond thanks for the response, it was insightful. However I seem to have found a simpler answer to my own question. I created my own interface
type UserCreator interface{
GetUser(*iam.GetUserInput) (*iam.GetUserOutput, error)
CreateUser(*iam.CreateUserInput) (*iam.CreateUserInput, error)
}
func CreateMyUser(iamSvc UserCreator, userName string) (string, error){
//first IAM call
_, err:=iamSvc.GetUser(&iam.GetUserInput{UserName: userName})
if err != nil {
fmt.Println("Failed to get user, attempting to create")
//second IAM call
_, err:=iamSvc.CreateUser(&iam.CreateUserInput{UserName: userName})
if err != nil {
log.Fatalf("Failed to create user\n", err )
}
}
}
And then for my test I just implement the interface, override these methods, and pass a mock:
type mockUserCreator{
Response string
}
func (m * mockUserCreator) GetUser(input *iam.GetUserInput)(*iam.GetUserOutput, error){
return &iam.GetUserOutput{}, nil
}
func (m * mockUserCreator) CreateUser(input *iam.CreateUserInput)(*iam.CreateUserOutput, error){
return &iam.CreateUserOutput{}, nil
}
func TestCreateMyUser(t *testing.T){
testcases:=[]struct{
TestName string
}{
{
TestName:"Some test"
}
}
for _, tt := range testcases{
t.Run(tt.TestName, func(t *testing.T){
m := mockUserCreator{}
mockUser := "TestUser"
_, err:= CreateMyUser(&m, mockUser)
if err != nil {
t.Error("TestCreateMyUser returned and error: %s", err)
}
}
}
}
I have the following production code:
func pullMessages(ctx context.Context, sub *pubsub.Subscription) {
err := sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
log.Printf("Got message: %q\n", string(msg.Data))
processMessage(msg)
msg.Ack()
})
if err != nil {
log.Printf("Receive: %v", err)
}
}
How can I verify processMessage is actually being called in a unit test ?
Quite simple: make your callback a named function instead of an anonymous one:
func pullMessages(ctx context.Context, sub *pubsub.Subscription) {
if err := sub.Receive(ctx, rcvCallback); err != nil {
log.Printf("Receiving message: %s",err)
}
}
func rcvCallback (ctx context.Context, msg *pubsub.Message) {
log.Printf("Got message: %q\n", string(msg.Data))
processMessage(msg)
msg.Ack()
}
Now, in your unit tests, you can create an instance of a Context and a Message and pass it to your function. However, in this case, that barely makes sense. You do little more than logging and acknowledging the message, functions which should be unit tested by the upstream project. Hence it would make more sense to construct an instance of message and unit test processMessage.
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?