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
A small SaaS company has built a Go HTTP API for managing users. The API uses the standard library net/http with Go 1.22+ routing and SQLite for storage. The team ships features quickly but has zero test coverage. A recent deployment broke the GET endpoint by returning an empty list instead of the actual users, and nobody caught it until a customer complained.
You have been asked to write a comprehensive test suite for this user API. The tests should cover all CRUD operations, verify error handling, and ensure that created users actually persist (can be fetched after creation).
Produce the following files:
handlers_test.go -- the main test file with all test functionstesthelpers_test.go -- reusable test helper functions (makeRequest, parseJSON, createTestUser)The tests must use httptest.NewRecorder and httptest.NewRequest from the standard library. Do NOT use external HTTP clients or test frameworks for making requests (testify assertions are acceptable).
The following files are provided as inputs. Extract them before beginning.
=============== FILE: go.mod =============== module userapi
go 1.23
require github.com/mattn/go-sqlite3 v1.14.22
=============== FILE: main.go =============== package main
import ( "database/sql" "encoding/json" "fmt" "log" "net/http" "os"
_ "github.com/mattn/go-sqlite3")
var db *sql.DB
func main() { var err error db, err = sql.Open("sqlite3", getDBPath()) if err != nil { log.Fatal(err) } defer db.Close() runMigrations(db)
mux := setupRoutes(db)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))}
func getDBPath() string { if p := os.Getenv("DB_PATH"); p != "" { return p } return "users.db" }
func runMigrations(database *sql.DB) {
_, err := database.Exec( CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) )
if err != nil {
log.Fatal(err)
}
}
func setupRoutes(database *sql.DB) *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("GET /api/users", handleListUsers(database)) mux.HandleFunc("POST /api/users", handleCreateUser(database)) mux.HandleFunc("GET /api/users/{id}", handleGetUser(database)) mux.HandleFunc("PUT /api/users/{id}", handleUpdateUser(database)) mux.HandleFunc("DELETE /api/users/{id}", handleDeleteUser(database)) return mux }
func handleListUsers(database *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { rows, err := database.Query("SELECT id, name, email, created_at FROM users") if err != nil { writeError(w, http.StatusInternalServerError, "failed to query users") return } defer rows.Close()
var users []map[string]any
for rows.Next() {
var id int
var name, email, createdAt string
rows.Scan(&id, &name, &email, &createdAt)
users = append(users, map[string]any{
"id": id, "name": name, "email": email, "created_at": createdAt,
})
}
if users == nil {
users = []map[string]any{}
}
writeJSON(w, http.StatusOK, map[string]any{"data": users})
}}
func handleCreateUser(database *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var body struct {
Name string json:"name"
Email string json:"email"
Password string json:"password"
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
if body.Name == "" || body.Email == "" || body.Password == "" {
writeError(w, http.StatusBadRequest, "name, email, and password are required")
return
}
result, err := database.Exec(
"INSERT INTO users (name, email, password_hash) VALUES (?, ?, ?)",
body.Name, body.Email, hashPassword(body.Password),
)
if err != nil {
writeError(w, http.StatusConflict, "email already exists")
return
}
id, _ := result.LastInsertId()
writeJSON(w, http.StatusCreated, map[string]any{
"data": map[string]any{"id": id, "name": body.Name, "email": body.Email},
})
}}
func handleGetUser(database *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var name, email, createdAt string err := database.QueryRow("SELECT name, email, created_at FROM users WHERE id = ?", id). Scan(&name, &email, &createdAt) if err != nil { writeError(w, http.StatusNotFound, "user not found") return } writeJSON(w, http.StatusOK, map[string]any{ "data": map[string]any{"id": id, "name": name, "email": email, "created_at": createdAt}, }) } }
func handleUpdateUser(database *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
var body struct {
Name string json:"name"
Email string json:"email"
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
if body.Name == "" && body.Email == "" {
writeError(w, http.StatusBadRequest, "at least one field (name or email) is required")
return
}
result, err := database.Exec("UPDATE users SET name = COALESCE(NULLIF(?, ''), name), email = COALESCE(NULLIF(?, ''), email) WHERE id = ?",
body.Name, body.Email, id)
if err != nil {
writeError(w, http.StatusInternalServerError, "update failed")
return
}
rows, _ := result.RowsAffected()
if rows == 0 {
writeError(w, http.StatusNotFound, "user not found")
return
}
writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"id": id, "name": body.Name, "email": body.Email}})
}}
func handleDeleteUser(database *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") result, err := database.Exec("DELETE FROM users WHERE id = ?", id) if err != nil { writeError(w, http.StatusInternalServerError, "delete failed") return } rows, _ := result.RowsAffected() if rows == 0 { writeError(w, http.StatusNotFound, "user not found") return } 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}}) }
func hashPassword(password string) string { return fmt.Sprintf("hashed_%s", password) // simplified for demo }