Around Go: Gin services · 3 MIN READ
Gin errors and tests: one response owner, explicit failure cases
Test handler contracts through ServeHTTP without confusing recovery with rollback.
Framework recovery can keep a panic from crashing a request-serving goroutine, but it cannot undo a committed database write or decide whether a remote operation succeeded. Expected application errors should travel through a deliberate mapping policy. Assign one layer to produce the response, and test both its status and public body rather than checking only that the handler returned.
gin · A MAP OF THE IDEA
- Use case returns cause
- Record expected error
- Map once
- Assert response and effects
Useful when
- Test handler contracts through ServeHTTP without confusing recovery with rollback.
- Makes the owner and the failure boundary explicit.
Watch out
- A test that asserts only status 200 can miss a leaked secret body or a second write after an error.
- Run native service and failure tests in your own development environment.
Implementation & operation cost: Additional state and tests must be justified by the demonstrated failure; this lesson is not a requirement for every Go program.
The contract and the failure
Framework recovery can keep a panic from crashing a request-serving goroutine, but it cannot undo a committed database write or decide whether a remote operation succeeded. Expected application errors should travel through a deliberate mapping policy. Assign one layer to produce the response, and test both its status and public body rather than checking only that the handler returned.
Work through the example
Use table-driven requests with method, path, input, verified identity and expected response. Check status, headers, JSON shape and whether forbidden side effects occurred. Inject a narrow application service and test the real storage adapter separately. Add panic recovery as a last-resort boundary, plus structured logging, without using panic as routine validation control flow.
package main
import (
"errors"
"fmt"
"net/http/httptest"
"github.com/gin-gonic/gin"
)
var errMissing = errors.New("missing")
func main() {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(func(c *gin.Context) {
c.Next()
if c.Writer.Written() || len(c.Errors) == 0 {
return
}
status := 500
if errors.Is(c.Errors.Last().Err, errMissing) {
status = 404
}
c.JSON(status, gin.H{"error": "request failed"})
})
r.GET("/orders/:id", func(c *gin.Context) { _ = c.Error(errMissing); c.Abort() })
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest("GET", "/orders/7", nil))
fmt.Println(w.Code, w.Body.String())
}The route records an application error and stops the pending handler chain. After Next returns, middleware maps a known missing value to 404 and writes once if no earlier layer committed the response. The public body is deliberately stable and does not expose a database error. Logging the detailed cause belongs at a protected reporting boundary.
Production decisions
A test that asserts only status 200 can miss a leaked secret body or a second write after an error. Returning an error from a helper does not automatically attach it to Gin. Recovery is not a transaction manager. Test malformed and unauthorized requests, missing resources, dependency cancellation, unknown outcomes and a panic after a simulated effect.
Explain it in an interview
A panic recovery middleware returns 500. Does that prove earlier side effects were rolled back? No. Recovery handles control flow; transaction and external-effect recovery need their own explicit ownership and reconciliation rules. Describe the state before the operation, its owner, the failure between steps and the evidence you would test. Distinguish what this example demonstrates from the infrastructure a production service still needs.
TAKE THIS WITH YOU
Test handler contracts through ServeHTTP without confusing recovery with rollback. No. Recovery handles control flow; transaction and external-effect recovery need their own explicit ownership and reconciliation rules.
Check your understanding
A panic recovery middleware returns 500. Does that prove earlier side effects were rolled back?
Go to the source
Progress stays in this browser. No account needed.