Testing the Full Server

Sometimes you need to test how your entire server works together, including routing. The httptest package provides tools for testing complete HTTP servers without requiring an actual network connection.

Setting up an httptest.Server and testing endpoints

The httptest.Server type creates a real HTTP server for testing purposes:

package main

import (
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestServerEndpoints(t *testing.T) {
	// Start the test server
	ts := setupTestServer(t)
	defer ts.Close()

	// Test the root endpoint
	resp, err := http.Get(ts.URL + "/")
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		io.Copy(io.Discard, resp.Body)
		resp.Body.Close()
	}()

	// Check status code
	if resp.StatusCode != http.StatusOK {
		t.Errorf("Expected status OK; got %v", resp.Status)
	}

	// Read and check the response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	if string(body) != "Hello world" {
		t.Errorf("Expected 'Hello world'; got %q", string(body))
	}
}

func setupTestServer(t *testing.T) *httptest.Server {
	t.Helper()
	// Set up your router with all handlers
	mux := http.NewServeMux()
	mux.HandleFunc("/", handler)

	// Create and return the test server
	return httptest.NewServer(mux)
}}

Benefits of Full Server Testing

Testing your complete server offers several advantages:

  1. Tests routing logic: Verifies URL patterns and HTTP methods are correctly mapped
  2. Tests middleware integration: Ensures middleware like authentication works properly
  3. Closer to real-world usage: Tests the API as a client would use it
  4. Exposes integration issues: Reveals problems that might not appear in isolated handler tests

By combining both handler-level testing and full server testing, you can build confidence in your API's correctness and reliability.

A pragmatic approach is to use full server testing and that is my recommendation.