Unit Testing With Gin-Gonic

Evan Bloemer picture Evan Bloemer · Dec 5, 2019 · Viewed 7.9k times · Source

My project is split into three main components: controllers, services, and models. When a route is queried via the URI, the controllers are called, which then call the services to interact with the models, which then interact with the database via gorm.

I am trying to write unit tests for the controllers, but I'm having a hard time understanding how to properly mock the services layer while mocking the gin layer. I can get a mocked gin context, but I'm not able to mock the service layer within my controller method. Below is my code:

resourceController.go

package controllers

import (
    "MyApi/models"
    "MyApi/services"
    "github.com/gin-gonic/gin"
    "net/http"
)

func GetResourceById(c *gin.Context) {
    id := c.Param("id")
    resource, err := services.GetResourceById(id)

    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"status": http.StatusBadRequest, "message": err})
        return
    } else if resource.ID == 0 {
        c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "Resource with id:"+id+" does not exist"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "id": resource.ID,
        "data1": resource.Data1,
        "data2": resource.Data2,
    })
}

I want to test that the c.JSON is returning with the proper http status and other data. I need to mock the id variable, err variable, and c.JSON function, but when I try to set the c.JSON function in the test to my new function, I get an error saying Cannot assign to c.JSON. Below is my attempt at writing a test:

resourceController_test.go

package controllers

import (
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "net/http/httptest"
    "testing"
)

func TestGetResourceById(t *testing.T) {
    var status int
    var body interface{}
    c, _ := gin.CreateTestContext(httptest.NewRecorder())
    c.JSON = func(stat int, object interface{}) {
        status = stat
        body = object
    }
    GetResourceById(c)
    assert.Equal(t, 4, 4)
}

How do I properly write a unit test to test whether the c.JSON is returning the proper values?

Answer

leaf bebop picture leaf bebop · Dec 5, 2019

You cannot modify a method of a type in Go. It is defined and immuatable by the package that defines the type at compile time. This is a design decision by Go. Simply don't do it.

You have already use httptest.NewRecorder() as a mock of gin.Context.ResponseWriter, which will records what is written to the response, including the c.JSON call. However, you need to keep a reference of the httptest.ReponseRecorder and then check it later. Note that you only have a marshalled JSON, so you need to unmarshal it to check content (as both Go map and JSON objects's order does not matter, checking marshalled string's equality is error-prone).

For example,

func TestGetResourceById(t *testing.T) {
    w := httptest.NewRecorder()
    c, _ := gin.CreateTestContext(w)
    GetResourceById(c)
    assert.Equal(t, 200, w.Code) // or what value you need it to be

    var got gin.H
    err := json.Unmarshal(w.Body.Bytes(), &got)
    if err != nil {
        t.Fatal(err)
    }
    assert.Equal(t, want, got) // want is a gin.H that contains the wanted map.
}