Description

Im trying to unit test some gin middeware. This middleware accepts a gin.Context and performs an action based on fields in the context.

I can see I can create a test context using gin.CreateTestContext() which returns a new context and an engine.

I would like to modify the context to set the preconditions for my test, but it is not clear how I would use the modified context.

How to reproduce

Middleware

func ensureAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        cookie := c.Keys[userDataKey].(loginCookie)

        if !cookie.LoggedIn && c.FullPath() != "/login" {
                        c.Abort()
            c.Redirect(302, "/login")
            return
        }

        c.Next()
    }
}

The middleware test

func TestEnsureAuthRedirectsToLogin(t *testing.T) {
    cookie := createDefaultCookie()
    w := httptest.NewRecorder()
    ctx, engine := gin.CreateTestContext(w)
    ctx.Set("UserData", cookie)

    req, _ := http.NewRequest(http.MethodGet, "/", nil)

    // What do I do with `ctx`? Is there a way to inject this into my test?

    engine.Use(ensureAuth())
    engine.ServeHTTP(w, req)

    assert.Equal(t, 302, w.Result().StatusCode)
    assert.Equal(t, "/login", w.Result().Header.Get(HeaderLocation))
}

Expectations

I should be able to inject the modified context in order to test my middleware

Actual result

There doesn't appear to be a way to inject the context

Environment

  • go version: 1.16
  • gin version (or commit ref): 1.6.3
  • operating system: macOS BigSur / arm64

Comment From: jimbirthday

@ri-ch I found the problem. When creating the context, you can see that the key is valuable at this time

Gin Unit testing middleware with CreateTestContext

But the problem lies in the function ServeHTTP()

Gin Unit testing middleware with CreateTestContext

You can see that the key in the current context has no value.

You can look at the source code of engine.pool.Get().(*Context)

// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() interface{} {
    if race.Enabled {
        race.Disable()
    }
    l, pid := p.pin()
    x := l.private
    l.private = nil
    if x == nil {
        // Try to pop the head of the local shard. We prefer
        // the head over the tail for temporal locality of
        // reuse.
        x, _ = l.shared.popHead()
        if x == nil {
            x = p.getSlow(pid)
        }
    }
    runtime_procUnpin()
    if race.Enabled {
        race.Enable()
        if x != nil {
            race.Acquire(poolRaceAddr(x))
        }
    }
    if x == nil && p.New != nil {
        x = p.New()
    }
    return x
}

The context at this time is no longer the first context, it is random.

This is my code

func TestEnsureAuthRedirectsToLogin(t *testing.T) {
    cookie := http.Cookie{
        Name:       "",
        Value:      "",
        Path:       "/login",
        Domain:     "",
        Expires:    time.Time{},
        RawExpires: "",
        MaxAge:     0,
        Secure:     false,
        HttpOnly:   false,
        SameSite:   0,
        Raw:        "",
        Unparsed:   nil,
    }
    w := httptest.NewRecorder()
    ctx, engine := gin.CreateTestContext(w)
    ctx.Set("UserData", cookie)
    req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/", nil)

    // What do I do with `ctx`? Is there a way to inject this into my test?

    engine.Use(ensureAuth())
    engine.ServeHTTP(w, req)

    assert.Equal(t, 302, w.Result().StatusCode)
    assert.Equal(t, "/login", w.Result().Header.Get("Path"))
}

func ensureAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        get, exists := c.Get("UserData")
        if !exists {
            fmt.Println("UserData not exists")
            c.Abort()
            return
        }
        cookie := get.(http.Cookie)
        path := cookie.Path
        fmt.Println("cookie path is", path)
        if c.FullPath() != "/login" {
            c.Abort()
            c.Redirect(302, "/login")
            return
        }
        c.Next()
    }
}

Description

Im trying to unit test some gin middeware. This middleware accepts a gin.Context and performs an action based on fields in the context.

I can see I can create a test context using gin.CreateTestContext() which returns a new context and an engine.

I would like to modify the context to set the preconditions for my test, but it is not clear how I would use the modified context.

How to reproduce

Middleware

``` func ensureAuth() gin.HandlerFunc { return func(c *gin.Context) { cookie := c.Keys[userDataKey].(loginCookie)

  if !cookie.LoggedIn && c.FullPath() != "/login" {
                    c.Abort()
      c.Redirect(302, "/login")
      return
  }

  c.Next()

} } ```

The middleware test

``` func TestEnsureAuthRedirectsToLogin(t *testing.T) { cookie := createDefaultCookie() w := httptest.NewRecorder() ctx, engine := gin.CreateTestContext(w) ctx.Set("UserData", cookie)

req, _ := http.NewRequest(http.MethodGet, "/", nil)

// What do I do with ctx? Is there a way to inject this into my test?

engine.Use(ensureAuth()) engine.ServeHTTP(w, req)

assert.Equal(t, 302, w.Result().StatusCode) assert.Equal(t, "/login", w.Result().Header.Get(HeaderLocation)) } ```

Expectations

I should be able to inject the modified context in order to test my middleware

Actual result

There doesn't appear to be a way to inject the context

Environment

  • go version: 1.16
  • gin version (or commit ref): 1.6.3
  • operating system: macOS BigSur / arm64

Comment From: cohendvir

Just encountered the exact same issue. did you ever figure out a way to solve this? currently, it works for me by using HandleContext and not ServeHTTP, however not sure what's the difference.

c.Request, err = http.NewRequest("POST", path, ioReader)
require.NoError(t, err)
e.HandleContext(c)

Comment From: cyclops1982

This seems to be duplicate of #1292 which has a workaround that works for me.