Go API testing patterns -- httptest setup, table-driven tests with subtests, test helpers, middleware testing, dependency injection with interfaces, database isolation, parallel tests, testify assertions, golden files
98
98%
Does it follow best practices?
Impact
99%
1.06xAverage score across 5 eval scenarios
Passed
No known issues
An e-commerce platform has a Go API for managing products. Products have a name, price (must be positive), category (must be one of: electronics, clothing, food, books), and optional description. The create endpoint validates all these fields and returns specific error messages.
The team wants comprehensive validation tests that cover every invalid input combination. They have asked you to write table-driven tests -- the standard Go idiom for testing multiple inputs -- with t.Run subtests so each case can be run and debugged individually.
Produce the following files:
handlers_test.go -- table-driven validation tests and happy path teststesthelpers_test.go -- reusable test helpersFocus on the validation tests for POST /api/products. Use table-driven tests with at least 5 different invalid input variations plus at least 1 valid case.
The following files are provided as inputs. Extract them before beginning.
=============== FILE: go.mod =============== module productapi
go 1.23
=============== FILE: main.go =============== package main
import ( "encoding/json" "log" "net/http" "strconv" "sync" )
type Product struct {
ID int json:"id"
Name string json:"name"
Price float64 json:"price"
Category string json:"category"
Description string json:"description,omitempty"
}
var ( products = make(map[int]*Product) nextID = 1 productsMu sync.Mutex )
func main() { mux := setupRoutes() log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", mux)) }
func setupRoutes() *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("GET /api/products", handleListProducts) mux.HandleFunc("POST /api/products", handleCreateProduct) mux.HandleFunc("GET /api/products/{id}", handleGetProduct) mux.HandleFunc("DELETE /api/products/{id}", handleDeleteProduct) return mux }
func resetState() { productsMu.Lock() defer productsMu.Unlock() products = make(map[int]*Product) nextID = 1 }
var validCategories = map[string]bool{ "electronics": true, "clothing": true, "food": true, "books": true, }
func handleListProducts(w http.ResponseWriter, r *http.Request) { productsMu.Lock() defer productsMu.Unlock()
list := make([]*Product, 0, len(products))
for _, p := range products {
list = append(list, p)
}
writeJSON(w, http.StatusOK, map[string]any{"data": list})}
func handleCreateProduct(w http.ResponseWriter, r *http.Request) {
var body struct {
Name string json:"name"
Price float64 json:"price"
Category string json:"category"
Description string json:"description"
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
return
}
if body.Name == "" {
writeError(w, http.StatusBadRequest, "name is required")
return
}
if body.Price <= 0 {
writeError(w, http.StatusBadRequest, "price must be greater than zero")
return
}
if body.Category == "" {
writeError(w, http.StatusBadRequest, "category is required")
return
}
if !validCategories[body.Category] {
writeError(w, http.StatusBadRequest, "category must be one of: electronics, clothing, food, books")
return
}
productsMu.Lock()
defer productsMu.Unlock()
p := &Product{
ID: nextID,
Name: body.Name,
Price: body.Price,
Category: body.Category,
Description: body.Description,
}
products[nextID] = p
nextID++
writeJSON(w, http.StatusCreated, map[string]any{"data": p})}
func handleGetProduct(w http.ResponseWriter, r *http.Request) { idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { writeError(w, http.StatusBadRequest, "invalid product ID") return }
productsMu.Lock()
defer productsMu.Unlock()
p, ok := products[id]
if !ok {
writeError(w, http.StatusNotFound, "product not found")
return
}
writeJSON(w, http.StatusOK, map[string]any{"data": p})}
func handleDeleteProduct(w http.ResponseWriter, r *http.Request) { idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { writeError(w, http.StatusBadRequest, "invalid product ID") return }
productsMu.Lock()
defer productsMu.Unlock()
if _, ok := products[id]; !ok {
writeError(w, http.StatusNotFound, "product not found")
return
}
delete(products, id)
w.WriteHeader(http.StatusNoContent)}
func writeJSON(w http.ResponseWriter, status int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) }
func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]any{"error": map[string]any{"message": message}}) }