gin/test_helpers.go
Abhiyan Khanal 4a0302068c 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 <noreply@anthropic.com>
2026-03-08 19:09:51 +05:45

85 lines
2.7 KiB
Go

// 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 (
"fmt"
"net/http"
"time"
)
// CreateTestContext returns a fresh Engine and a Context associated with it.
// This is useful for tests that need to set up a new Gin engine instance
// along with a context, for example, to test middleware that doesn't depend on
// specific routes. The ResponseWriter `w` is used to initialize the context's writer.
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
r = New()
c = r.allocateContext(0)
c.reset()
c.writermem.reset(w)
return
}
// CreateTestContextOnly returns a fresh Context associated with the provided Engine `r`.
// This is useful for tests that operate on an existing, possibly pre-configured,
// Gin engine instance and need a new context for it.
// The ResponseWriter `w` is used to initialize the context's writer.
// The context is allocated with the `maxParams` setting from the provided engine.
func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
c = r.allocateContext(r.maxParams)
c.reset()
c.writermem.reset(w)
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 {
client := &http.Client{
Timeout: 100 * time.Millisecond,
}
for i := 0; i < maxAttempts; i++ {
resp, err := client.Get(url)
if err == nil {
resp.Body.Close()
return nil
}
// Exponential backoff: 10ms, 20ms, 40ms, 80ms, 160ms...
backoff := time.Duration(10*(1<<uint(i))) * time.Millisecond
if backoff > 500*time.Millisecond {
backoff = 500 * time.Millisecond
}
time.Sleep(backoff)
}
return fmt.Errorf("server at %s did not become ready after %d attempts", url, maxAttempts)
}