httptest para Pruebas de Integración de Handlers
El paquete net/http/httptest prueba handlers HTTP sin configuración manual de TCP.
Busca en todas las páginas de la documentación
El paquete net/http/httptest prueba handlers HTTP sin configuración manual de TCP.
ResponseRecorder soporta pruebas rápidas de handlers estilo unitario; NewServer soporta pruebas de integración que ejercitan viajes de ida y vuelta reales de http.Client.
Las pruebas de handlers deben verificar códigos de estado, cabeceras y cuerpos contra el comportamiento esperado.
httptest.NewRequest construye valores *http.Request con método, URL y cuerpo.
httptest.ResponseRecorder implementa http.ResponseWriter y registra lo que el handler escribió.
httptest.NewServer envuelve un handler con un http.Server efímero y devuelve una URL base para llamadas de cliente.
Las pruebas basadas en tablas mantienen las matrices de rutas mantenibles.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
func TestHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/items", strings.NewReader(`{"name":"a"}`))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
handler(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
}
}Cuándo usar esto:
package main
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
type item struct {
Name string `json:"name"`
}
func itemsHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode([]item{{Name: "seed"}})
case http.MethodPost:
var in item
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(in)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func TestItemsHandler_recorder(t *testing.T) {
tests := []struct {
name string
method string
body string
want int
}{
{"get ok", http.MethodGet, "", http.StatusOK},
{"post ok", http.MethodPost, `{"name":"x"}`, http.StatusCreated},
{"post bad json", http.MethodPost, `{`, http.StatusBadRequest},
{"patch not allowed", http.MethodPatch, "", http.StatusMethodNotAllowed},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var body io.Reader
if tc.body != "" {
body = strings.NewReader(tc.body)
}
req := httptest.NewRequest(tc.method, "/items", body)
if tc.body != "" {
req.Header.Set("Content-Type", "application/json")
}
rec := httptest.NewRecorder()
itemsHandler(rec, req)
if rec.Code != tc.want {
t.Fatalf("got %d want %d body=%q", rec.Code, tc.want, rec.Body.String())
}
})
}
}
func TestItemsHandler_newServer(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(itemsHandler))
defer srv.Close()
resp, err := http.Get(srv.URL + "/items")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status=%d", resp.StatusCode)
}
}Lo que esto demuestra:
ResponseRecorder para invocación directa de handlers.strings.NewReader en NewRequest.httptest.NewServer usando http.Get contra srv.URL.NewRecorder devuelve un ResponseRecorder con los campos Code, Header y Body poblados después de ServeHTTP.NewRequest establece URL.Path y Host adecuados para el código del handler que lee r.URL y r.Host.NewServer elige un puerto libre, comienza a escuchar en una goroutine y bloquea el apagado hasta Close().NewTLSServer proporciona TLS para probar rutas de validación de certificados.| Patrón | Herramienta | Mejor para |
|---|---|---|
| Prueba unitaria de handler | ResponseRecorder | Afirmaciones de estado/cuerpo/cabecera |
| Prueba de middleware | Recorder + stub next | Comportamiento de acceso directo |
| Prueba de enrutamiento de Mux | mux.ServeHTTP(rec, req) | Coincidencia de patrones |
| Integración de cliente | NewServer + http.Client | Viaje de ida y vuelta completo |
// Afirma JSON sin comparaciones de cadenas frágiles:
var got item
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
t.Fatal(err)
}
// Ejecuta pruebas de servidor en paralelo con su propio srv:
t.Parallel()
srv := httptest.NewServer(handler)
t.Cleanup(srv.Close)Close() en NewServer - Fugas de goroutines y puertos en ejecuciones de prueba repetidas. Solución: defer srv.Close() o t.Cleanup(srv.Close).t.Parallel. Solución: Crear un http.NewServeMux() nuevo por prueba o usar t.Parallel solo con handlers aislados.json.RawMessage.Content-Type en POST - Los errores del decodificador enmascaran errores de lógica del handler. Solución: Establecer cabeceras en las pruebas exactamente como lo harían los clientes.http.DefaultClient sin tiempo de espera en pruebas - Pruebas colgadas si el servidor se detiene. Solución: http.Client{Timeout: ...} corto en pruebas de integración.rec.Code por defecto es 200 - Cero significa no establecido hasta WriteHeader o la primera Write. Solución: Comprobar Code después de que el handler devuelva; el valor por defecto es 200 solo después de escribir.
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| ginkgo/gomega HTTP matchers | Se prefieren especificaciones estilo BDD | El paquete testing estándar es suficiente |
| integración dockerizada | Pila completa con DB real | La lógica del handler solo necesita pruebas en memoria |
| fixtures grabadas | Contratos estables de API de terceros | Pruebas de tus propios handlers en proceso |
net.Listen manual | Configuración personalizada de TLS o HTTP/2 | httptest.NewServer cubre la mayoría de los casos |
Usa ResponseRecorder para pruebas unitarias directas de handlers.
Usa NewServer cuando la prueba deba ejercitar http.Client, redirecciones o TLS.
Establece Authorization o cookies de sesión en httptest.NewRequest antes de invocar la cadena de handlers.
Sí - llama a mux.ServeHTTP(rec, req) con el método y la ruta que coincidan con los patrones registrados.
Lee rec.Header().Get("Content-Type") después de que el handler devuelva.
rec.Result() devuelve una instantánea de *http.Response para casos avanzados.
Usa context.Background() por defecto.
Usa req.WithContext(ctx) para probar el comportamiento de cancelación.
Pasa un ResponseWriter personalizado o usa el recorder y afirma que el wrapper observó el estado.
NewUnstartedServer más ConfigureServer manual puede habilitar HTTP/2.
La mayoría de las pruebas de handlers no requieren comportamiento específico de HTTP/2.
Construye un cuerpo multipart.Writer, establece la cabecera Content-Type con el límite y pasa el buffer a NewRequest.
Sí, cuando cada subprueba posea su propio servidor o mux.
Evita pruebas paralelas que muten el http.DefaultServeMux global compartido.
Redirige la salida de log a bytes.Buffer en las pruebas o inyecta una interfaz de logger en los handlers.
Inicia un NewServer para el upstream falso y monta ReverseProxy como el handler bajo prueba.
Ejercita el middleware de recuperación con un handler que entra en pánico y afirma el estado 500 en el recorder.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado de GC Green Tea, go fix modernizers - verificar parche en la compilación), chi (última - verificar en la compilación), gin (última - verificar en la compilación), echo (última - verificar en la compilación), google.golang.org/grpc (última - verificar en la compilación), sigs.k8s.io/controller-runtime (última - verificar en la compilación), kubebuilder (última - verificar en la compilación), tinygo (última - verificar objetivos de placa en la compilación), wazero (última - verificar en la compilación), y golangci-lint (última - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 19 jul 2026