httptest para Testes de Integração de Handlers
O pacote net/http/httptest testa handlers HTTP sem configuração manual de TCP.
Busque em todas as páginas da documentação
O pacote net/http/httptest testa handlers HTTP sem configuração manual de TCP.
ResponseRecorder suporta testes rápidos de handler no estilo unitário; NewServer suporta testes de integração que exercitam viagens de ida e volta reais do http.Client.
Testes de handler devem assertar códigos de status, cabeçalhos e corpos contra o comportamento esperado.
httptest.NewRequest constrói valores *http.Request com método, URL e corpo.
httptest.ResponseRecorder implementa http.ResponseWriter e registra o que o handler escreveu.
httptest.NewServer envolve um handler com um http.Server efêmero e retorna uma URL base para chamadas de cliente.
Testes orientados por tabela mantêm matrizes de rotas manuteníveis.
Cartão de receita de referência rápida - pronto para copiar e colar.
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())
}
}Quando usar isso:
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)
}
}O que isso demonstra:
ResponseRecorder para invocação direta de handler.strings.NewReader em NewRequest.httptest.NewServer usando http.Get contra srv.URL.NewRecorder retorna um ResponseRecorder com os campos Code, Header e Body preenchidos após ServeHTTP.NewRequest define URL.Path e Host adequados para código de handler que lê r.URL e r.Host.NewServer seleciona uma porta livre, começa a escutar em uma goroutine e bloqueia o desligamento em Close().NewTLSServer fornece TLS para testar caminhos de validação de certificado.| Padrão | Ferramenta | Melhor para |
|---|---|---|
| Teste unitário de handler | ResponseRecorder | Asserções de status/corpo/cabeçalho |
| Teste de middleware | Recorder + stub next | Comportamento de curto-circuito |
| Teste de roteamento de Mux | mux.ServeHTTP(rec, req) | Correspondência de padrões |
| Integração de cliente | NewServer + http.Client | Viagem de ida e volta completa |
// Assert JSON sem comparações de string frágeis:
var got item
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
t.Fatal(err)
}
// Execute testes de servidor em paralelo com seu próprio srv:
t.Parallel()
srv := httptest.NewServer(handler)
t.Cleanup(srv.Close)Close() em NewServer - Vazamentos de goroutines e portas em execuções de teste repetidas. Correção: defer srv.Close() ou t.Cleanup(srv.Close).t.Parallel. Correção: Crie um http.NewServeMux() novo por teste ou use t.Parallel apenas com handlers isolados.json.RawMessage.http.DefaultClient sem timeout em testes - Testes travam se o servidor parar. Correção: http.Client{Timeout: ...} curto em testes de integração.rec.Code é 200 por padrão - Zero significa não definido até WriteHeader ou o primeiro Write. Correção: Verifique Code após o retorno do handler; o padrão é 200 apenas após a escrita.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| ginkgo/gomega HTTP matchers | Preferência por specs estilo BDD | Pacote testing padrão é suficiente |
| integração dockerizada | Stack completo com DB real | Lógica do handler só precisa de testes em memória |
| fixtures gravadas | Contratos de API de terceiros estáveis | Testando seus próprios handlers em processo |
net.Listen manual | Configuração TLS ou HTTP/2 personalizada | httptest.NewServer cobre a maioria dos casos |
Use ResponseRecorder para testes unitários diretos de handler.
Use NewServer quando o teste deve exercitar http.Client, redirecionamentos ou TLS.
Defina Authorization ou cookies de sessão em httptest.NewRequest antes de invocar a cadeia de handlers.
Sim - chame mux.ServeHTTP(rec, req) com método e caminho correspondendo aos padrões registrados.
Leia rec.Header().Get("Content-Type") após o retorno do handler.
rec.Result() retorna um snapshot de *http.Response para casos avançados.
Ele usa context.Background() por padrão.
Use req.WithContext(ctx) para testar o comportamento de cancelamento.
Passe um ResponseWriter personalizado ou use o recorder e verifique se o wrapper observou o status.
NewUnstartedServer mais ConfigureServer manual pode habilitar HTTP/2.
A maioria dos testes de handler não requer comportamento específico de HTTP/2.
Construa o corpo multipart.Writer, defina o cabeçalho Content-Type com o boundary e passe o buffer para NewRequest.
Sim, quando cada subteste possui seu próprio servidor ou mux.
Evite testes paralelos que modifiquem o http.DefaultServeMux global compartilhado.
Redirecione a saída de log para bytes.Buffer em testes ou injete uma interface de logger nos handlers.
Inicie um NewServer para o upstream falso e monte ReverseProxy como o handler sob teste.
Exercite o middleware de recuperação com um handler que entra em pânico e asserta o status 500 no recorder.
Versões de Stack: Esta página foi escrita para Go 1.26.x (padrão GC Green Tea, go fix modernizers - verifique o patch na compilação), chi (última - verifique na compilação), gin (última - verifique na compilação), echo (última - verifique na compilação), google.golang.org/grpc (última - verifique na compilação), sigs.k8s.io/controller-runtime (última - verifique na compilação), kubebuilder (última - verifique na compilação), tinygo (última - verifique os alvos de placa na compilação), wazero (última - verifique na compilação) e golangci-lint (última - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026