Unit Testing With Gin-Gonic

13,373

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.
}
Share:
13,373
Evan Bloemer
Author by

Evan Bloemer

Updated on June 23, 2022

Comments

  • Evan Bloemer
    Evan Bloemer almost 2 years

    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?