gin
gin copied to clipboard
Unit testing middleware with CreateTestContext
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
@ri-ch I found the problem. When creating the context, you can see that the key is valuable at this time
But the problem lies in the function ServeHTTP()
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
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)
This seems to be duplicate of #1292 which has a workaround that works for me.