go testing http

Test de cliente HTTP

Usar httptest.NewServer para probar rutas, status codes y decode sin llamar servicios externos reales.

func TestGetRecord(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/records/1" {
			t.Errorf("unexpected URL path: %s", r.URL.Path)
			http.Error(w, "unexpected path", http.StatusBadRequest)
			return
		}

		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"id":1,"label":"example"}`))
	}))
	defer server.Close()

	client := NewClient(server.URL, server.Client())

	got, err := client.GetRecord(context.Background(), 1)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	want := &Record{ID: 1, Label: "example"}
	if diff := cmp.Diff(want, got); diff != "" {
		t.Errorf("unexpected record (-want +got):\n%s", diff)
	}
}

Test de status no-2xx

Probar que un status de error se convierta en error de dominio o adapter.

func TestGetRecordUnexpectedStatusCode(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer server.Close()

	client := NewClient(server.URL, server.Client())

	got, err := client.GetRecord(context.Background(), 1)
	if !errors.Is(err, ErrUnexpectedStatusCode) {
		t.Fatalf("expected status code error, got: %v", err)
	}
	if got != nil {
		t.Fatalf("expected nil response, got: %v", got)
	}
}

Test de timeout

Preferir mocks para tests rápidos y deterministas.

type timeoutHTTPClient struct{}

func (c *timeoutHTTPClient) Do(req *http.Request) (*http.Response, error) {
	return nil, context.DeadlineExceeded
}
func TestGetRecordTimeout(t *testing.T) {
	client := NewClient("https://example.com", &timeoutHTTPClient{})

	got, err := client.GetRecord(context.Background(), 1)
	if !errors.Is(err, context.DeadlineExceeded) {
		t.Fatalf("expected timeout, got: %v", err)
	}
	if got != nil {
		t.Fatalf("expected nil response, got: %v", got)
	}
}

Un test con time.Sleep sirve para probar integración real del timeout, pero normalmente debe quedar separado, lento o skipeado.

Test de handler

Usar httptest.NewRecorder y mocks de la capa de aplicación.

func TestGetRecordHandler(t *testing.T) {
	service := &mockService{
		Record: &Record{ID: 1, Label: "example"},
	}
	handler := NewHandler(service)

	req := httptest.NewRequest(http.MethodGet, "/records/1", nil)
	req.SetPathValue("id", "1")

	rr := httptest.NewRecorder()

	http.HandlerFunc(handler.GetRecord).ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("wrong status code: got %d", rr.Code)
	}

	var got Record
	if err := json.NewDecoder(rr.Body).Decode(&got); err != nil {
		t.Fatalf("error decoding response: %v", err)
	}
}

Checklist