fabian heredia
fabian heredia

Reputation: 43

How to mock a gin.Context?

Hi I've been trying to mock a gin.Context but I have not been able to make it work I was trying what they did in this solution but it does not work with my router this is the error I have been getting

r.POST("/urls", urlRepo.CreateUrl)

cannot use urlRepo.CreateUrl (value of type func(c controllers.Icontext)) as gin.HandlerFunc value in argument to r.POSTcompilerIncompatibleAssign

This is the interface I created to later mock and the method in which I will be testing

type Icontext interface {
  BindJSON(obj interface{}) error
  JSON(code int, obj interface{})
  AbortWithStatus(code int)
  AbortWithStatusJSON(code int, jsonObj interface{})

}

func (repository *UrlRepo) CreateUrl(c Icontext) {
    var url models.Url
    c.BindJSON(&url)
    if !validators.IsCreateJsonCorrect(url) {
        c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Error format in Short or Full"})
        return
    }
    err := repository.reposito.CreateUrl(repository.Db, &url)
    if err != nil {
        c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
        return
    }
    c.JSON(http.StatusOK, url)
} 

Instead of

func (repository *UrlRepo) CreateUrl(c Icontext)

it was

func (repository *UrlRepo) CreateUrl(c *gin.Context) 

Upvotes: 3

Views: 4806

Answers (2)

blackgreen
blackgreen

Reputation: 44797

Strictly speaking, you can't "mock" a *gin.Context in a meaningful way, because it's a struct with unexported fields and methods.

Furthermore, you can't pass to r.POST() a function whose type is not a gin.HandlerFunc, defined as func(*gin.Context). The type of your handler CreateUrl(c Icontext) simply doesn't match.

If your goal is to unit test a Gin handler, you definitely don't have to mock the *gin.Context. What you should do is to set test values into it. For that, you can simply use gin.CreateTestContext() and manually init some of it fields. More info here.

If for some other reason, your goal is to provide an alternate implementation of a functionality of *gin.Context for use inside your handler, what you can do is define your own type with your own alternative methods and embed the *gin.Context in it.

In practice:

type MyGinContext struct {
    *gin.Context
}

func (m *MyGinContext) BindJSON(obj interface{}) error {
    fmt.Println("my own BindJSON")
    return m.Context.BindJSON(obj) // or entirely alternative implementation
}

// Using the appropriate function signature now
func (repository *UrlRepo) CreateUrl(c *gin.Context) {
    myCtx := &MyGinContext{c}

    var url models.Url
    _ = myCtx.BindJSON(&url) // will also print "my own BindJSON"
    // ...

    // other gin.Context methods are promoted and available on MyGinContext
    myCtx.Status(200)
} 

But honestly I'm not sure why you would want to override some methods of the *gin.Context. If you want to provide different binding logic, or even different rendering, you can implement the interfaces that are already exposed by the library. For example:

Implement a binding:

c.ShouldBindWith() takes as second parameter an interface binding.Binding which you can implement:

type MyBinder struct {
}

func (m *MyBinder) Name() string {
    return "foo"
}

func (m *MyBinder) Bind(*http.Request, interface{}) error {
    // stuff
    return nil
}

func MyHandler(c *gin.Context) {
   var foo struct{/*fields*/}
   c.ShouldBindWith(&foo, &MyBinder{})
}

Implement a renderer:

type MyRenderer struct {
}

type Render interface {
func (m *MyRenderer) Render(http.ResponseWriter) error {
    // ...
    return nil
}

func (m *MyRenderer) WriteContentType(w http.ResponseWriter) {
    header := w.Header()
    if val := header["Content-Type"]; len(val) == 0 {
        header["Content-Type"] = "application/foo+bar"
    }
}

func MyHandler(c *gin.Context) {
   c.Render(200, &MyRenderer{})
}

Upvotes: 2

Nacho Nieva
Nacho Nieva

Reputation: 181

if you are using gin-gonic as your http router, the param for your entry point should be a *gin.Context.

So, for instance, you should be replacing this:

func (repository *UrlRepo) CreateUrl(c Icontext) {

With this

func (repository *UrlRepo) CreateUrl(c *gin.Context) {

This way you should be able to use a mock gin context as a parameter for your unit test

Upvotes: 0

Related Questions