liveBook · Manning

Test double

As explained in Go in Action, Second Edition

A test double is a substitute component that provides controlled behavior or data in place of a real dependency during testing.

A test double is a simplified implementation used only during testing. It replaces a real dependency, such as a database or external service, with controlled behavior or data so the code under test can be exercised in isolation. section 11.3.2

In Go, interfaces make substitution natural. HTTP handlers depend on the narrow domain.ArticleReader interface rather than concrete storage, allowing a real BoltStore in production and a small test implementation in tests. section 11.3.2

Choose the double according to the test’s purpose:

  • Use a stub when the test needs predetermined data, such as a fixed list of articles. section 11.3.2
  • Use a mock when the test must verify interactions, such as whether a method received expected arguments; mocking libraries can support this. section 11.3.2
  • Keep the double behind the same interface as the production dependency, preserving interchangeable behavior through the interface contract. section 11.4

Isolating an HTTP handler

Construct Handlers with a test implementation of domain.ArticleReader that returns canned articles. Register the handler on an isolated http.ServeMux, then test query parsing, HTTP method handling, and JSON responses without opening a database or contacting an external service. section 11.3.2

Listing 11.4 HTTP handlers with an injected ArticleReader
package handlers

import (
    "encoding/json"
    "net/http"
    "strconv"

    "github.com/YOUR_USERNAME/go-news/domain"
)

type Handlers struct { #1
    articles domain.ArticleReader
}

func New(articles domain.ArticleReader) *Handlers { #2
    return &Handlers{
        articles: articles,
    }
}

func (h *Handlers) RegisterRoutes(mux *http.ServeMux) { #3
    mux.HandleFunc("/articles", h.articlesHandler)
}

func (h *Handlers) articlesHandler(w http.ResponseWriter, r *http.Request) { #4
    if r.Method != http.MethodGet {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }

    n := 10
    if countStr := r.URL.Query().Get("count"); countStr != "" { #5
        if count, err := strconv.Atoi(countStr); err == nil && count > 0 {
            n = count
        }
    }

    articles := h.articles.GetRecent(n)

    w.Header().Set("Content-Type", "application/json") #6
    if err := json.NewEncoder(w).Encode(articles); err != nil {
        http.Error(w, "failed to encode response", http.StatusInternalServerError)
        return
    }
}

Use the injected ArticleReader handler listing to trace dependency injection, route registration, method validation, and query handling. section 11.3.2

How Go in Action, Second Edition covers it

How deep it goes
Defines stubs and mocks and demonstrates interface substitution, but defers comprehensive HTTP test examples.
Examples
Uses a canned ArticleReader implementation to test handlers without the production BoltStore.
What it assumes
Assumes familiarity with Go interfaces, HTTP handlers, and domain-versus-infrastructure separation.
What you can do after
Apply it via Go interfaces to test handlers independently from storage and external services.
How it connects
Connects test doubles to interfaces, dependency inversion, the Liskov substitution principle, and isolated unit testing.

Also explained in

Compare how 6 books explain test double →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go in Action, Second Edition. The section links point to the parts of the book it draws on.