SyntaxStudy
Sign Up
Go Testing HTTP Handlers with httptest
Go Beginner 1 min read

Testing HTTP Handlers with httptest

The `net/http/httptest` package provides utilities for testing HTTP code without starting a real server. `httptest.NewRecorder()` returns a `ResponseRecorder` that implements `http.ResponseWriter` and records the status code, headers, and body written by the handler. `httptest.NewRequest` creates an `*http.Request` suitable for testing. For integration-level HTTP tests, `httptest.NewServer` starts a real HTTP server on a random port and returns its URL. Tests can make real HTTP requests to this server, which exercises the full request/response path including middleware chains. The server is closed with `defer ts.Close()` to release the port at the end of the test. Dependency injection is the key to testable Go HTTP handlers. Instead of constructing dependencies inside handlers, pass them via struct fields or function closures. This allows tests to inject mock or in-memory implementations of databases, caches, and external services, making tests fast, deterministic, and independent of external infrastructure.
Example
package handler_test

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
)

type UserStore interface {
    GetUser(id int) (string, bool)
}

type fakeStore struct {
    users map[int]string
}

func (f *fakeStore) GetUser(id int) (string, bool) {
    name, ok := f.users[id]
    return name, ok
}

func userHandler(store UserStore) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        id := 1 // simplified; real code uses r.PathValue
        name, ok := store.GetUser(id)
        if !ok {
            http.NotFound(w, r)
            return
        }
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"name": name})
    }
}

func TestUserHandler(t *testing.T) {
    store := &fakeStore{users: map[int]string{1: "Alice"}}
    handler := userHandler(store)

    // Unit-level: recorder test
    req  := httptest.NewRequest(http.MethodGet, "/users/1", nil)
    rec  := httptest.NewRecorder()
    handler.ServeHTTP(rec, req)

    if rec.Code != http.StatusOK {
        t.Errorf("expected 200, got %d", rec.Code)
    }
    var body map[string]string
    json.NewDecoder(rec.Body).Decode(&body)
    if body["name"] != "Alice" {
        t.Errorf("expected Alice, got %q", body["name"])
    }

    // Integration-level: real server
    ts := httptest.NewServer(handler)
    defer ts.Close()

    resp, _ := http.Get(ts.URL + "/users/1")
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        t.Errorf("server test: expected 200, got %d", resp.StatusCode)
    }
}

This is the last lesson in this section.

Create a free account to earn a certificate