Merge 3f607b8662710b3602b03817cfc2f171416f6591 into d3ffc9985281dcf4d3bef604cce4e662b1a327a6

This commit is contained in:
Abhiyan Khanal 2026-03-17 13:55:19 +09:00 committed by GitHub
commit 67cf55e6f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 125 additions and 0 deletions

View File

@ -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 {

101
test_helpers_test.go Normal file
View File

@ -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)
}