From 4a0302068ca0b73599cea9cbe83fe0f1b8e07114 Mon Sep 17 00:00:00 2001 From: Abhiyan Khanal Date: Sat, 7 Mar 2026 17:08:43 +0545 Subject: [PATCH 1/2] feat(test): add RunTestHandler to flush status in test contexts When using CreateTestContext directly to test handlers, ctx.Status() sets the status internally but it is not flushed to the underlying httptest.ResponseRecorder. RunTestHandler creates a test context, runs the handler chain, and calls WriteHeaderNow() so that the status code is properly reflected in the ResponseWriter. Co-Authored-By: Claude Opus 4.6 --- test_helpers.go | 24 ++++++++++ test_helpers_test.go | 101 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 test_helpers_test.go diff --git a/test_helpers.go b/test_helpers.go index 20d20032..7bd09fc6 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -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 { diff --git a/test_helpers_test.go b/test_helpers_test.go new file mode 100644 index 00000000..dcdf76da --- /dev/null +++ b/test_helpers_test.go @@ -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("GET", "/", 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("POST", "/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("GET", "/", 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("PUT", "/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) +} From 3f607b8662710b3602b03817cfc2f171416f6591 Mon Sep 17 00:00:00 2001 From: Abhiyan Khanal Date: Sun, 8 Mar 2026 18:56:15 +0545 Subject: [PATCH 2/2] fix(test): use stdlib constants for HTTP methods in test helpers Replace string literals with http.Method* constants to satisfy the usestdlibvars linter. Co-Authored-By: Claude Opus 4.6 --- test_helpers_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test_helpers_test.go b/test_helpers_test.go index dcdf76da..fe800811 100644 --- a/test_helpers_test.go +++ b/test_helpers_test.go @@ -27,7 +27,7 @@ func TestRunTestHandlerFlushesStatusCode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(http.MethodGet, "/", nil) code := tt.statusCode c := RunTestHandler(w, req, func(c *Context) { @@ -42,7 +42,7 @@ func TestRunTestHandlerFlushesStatusCode(t *testing.T) { func TestRunTestHandlerMultipleHandlers(t *testing.T) { w := httptest.NewRecorder() - req := httptest.NewRequest("POST", "/resource", nil) + req := httptest.NewRequest(http.MethodPost, "/resource", nil) callOrder := []string{} @@ -65,7 +65,7 @@ func TestRunTestHandlerMultipleHandlers(t *testing.T) { func TestRunTestHandlerDefaultStatus(t *testing.T) { w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(http.MethodGet, "/", nil) RunTestHandler(w, req, func(c *Context) { // Handler that does not set a status explicitly. @@ -76,7 +76,7 @@ func TestRunTestHandlerDefaultStatus(t *testing.T) { func TestRunTestHandlerSetsRequest(t *testing.T) { w := httptest.NewRecorder() - req := httptest.NewRequest("PUT", "/items/42", nil) + req := httptest.NewRequest(http.MethodPut, "/items/42", nil) c := RunTestHandler(w, req, func(c *Context) { c.Status(http.StatusOK)