Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit testing middleware with CreateTestContext #2816

Open
ri-ch opened this issue Aug 11, 2021 · 3 comments
Open

Unit testing middleware with CreateTestContext #2816

ri-ch opened this issue Aug 11, 2021 · 3 comments

Comments

@ri-ch
Copy link

ri-ch commented Aug 11, 2021

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
@jimbirthday
Copy link

jimbirthday commented Aug 12, 2021

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

image

But the problem lies in the function ServeHTTP()

image

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

@cohendvir
Copy link

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)

@cyclops1982
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants