Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@ func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
return
}

// RunTestHandler creates a test context, assigns the given request, executes
// the provided handler chain, and flushes the status code to the underlying
// ResponseWriter. This solves the problem where ctx.Status() sets the status
// internally but does not flush it to an httptest.ResponseRecorder when using
// CreateTestContext directly.
//
// Example usage:
//
// w := httptest.NewRecorder()
// req := httptest.NewRequest("POST", "/resource", nil)
// c := gin.RunTestHandler(w, req, func(c *gin.Context) {
// c.Status(http.StatusCreated)
// })
// // w.Code is now 201, not 200
func RunTestHandler(w http.ResponseWriter, req *http.Request, handlers ...HandlerFunc) *Context {
c, _ := CreateTestContext(w)
c.Request = req
c.handlers = handlers
c.index = -1
c.Next()
c.Writer.WriteHeaderNow()
return c
}

// waitForServerReady waits for a server to be ready by making HTTP requests
// with exponential backoff. This is more reliable than time.Sleep() for testing.
func waitForServerReady(url string, maxAttempts int) error {
Expand Down
101 changes: 101 additions & 0 deletions test_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package gin

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestRunTestHandlerFlushesStatusCode(t *testing.T) {
tests := []struct {
name string
statusCode int
}{
{"201 Created", http.StatusCreated},
{"204 No Content", http.StatusNoContent},
{"400 Bad Request", http.StatusBadRequest},
{"404 Not Found", http.StatusNotFound},
{"500 Internal Server Error", http.StatusInternalServerError},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/", nil)
code := tt.statusCode

c := RunTestHandler(w, req, func(c *Context) {
c.Status(code)
})

assert.Equal(t, code, w.Code)
assert.Equal(t, code, c.Writer.Status())
})
}
}

func TestRunTestHandlerMultipleHandlers(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/resource", nil)

callOrder := []string{}

middleware := func(c *Context) {
callOrder = append(callOrder, "middleware")
c.Next()
}

handler := func(c *Context) {
callOrder = append(callOrder, "handler")
c.Status(http.StatusCreated)
}

c := RunTestHandler(w, req, middleware, handler)

assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, http.StatusCreated, c.Writer.Status())
assert.Equal(t, []string{"middleware", "handler"}, callOrder)
}

func TestRunTestHandlerDefaultStatus(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/", nil)

RunTestHandler(w, req, func(c *Context) {
// Handler that does not set a status explicitly.
})

assert.Equal(t, http.StatusOK, w.Code)
}

func TestRunTestHandlerSetsRequest(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPut, "/items/42", nil)

c := RunTestHandler(w, req, func(c *Context) {
c.Status(http.StatusOK)
})

assert.Equal(t, req, c.Request)
}

func TestCreateTestContextBackwardCompatible(t *testing.T) {
// Verify that CreateTestContext still behaves the same way:
// status is stored internally but NOT flushed to the ResponseRecorder.
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Status(http.StatusCreated)

// The internal status should be set.
assert.Equal(t, http.StatusCreated, c.Writer.Status())

// But w.Code should still be the default 200 because WriteHeaderNow
// was never called. This confirms backward compatibility.
assert.Equal(t, http.StatusOK, w.Code)
}