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 Go API sends notifications to users through an external email service. The handler currently calls the email service directly, making it impossible to test without sending real emails. The team wants to refactor to use an interface for the email dependency and write tests with a mock implementation.
You have been asked to:
Produce the following files:
handlers.go -- refactored handlers that accept the EmailSender interfacehandlers_test.go -- tests with mock EmailSender implementationtesthelpers_test.go -- reusable test helpersThe interface and mock should be simple -- a hand-written struct, not a code-generated mock from gomock or mockery.
The following files are provided as inputs. Extract them before beginning.
=============== FILE: go.mod =============== module notifyapi
go 1.23
=============== FILE: main.go =============== package main
import ( "encoding/json" "fmt" "log" "net/http" "sync" )
type User struct {
ID int json:"id"
Name string json:"name"
Email string json:"email"
}
type Notification struct {
ID int json:"id"
UserID int json:"user_id"
Message string json:"message"
Status string json:"status"
}
var ( users = map[int]*User{1: {ID: 1, Name: "Alice", Email: "alice@example.com"}} notifications []Notification notifMu sync.Mutex nextNotifID = 1 )
func main() { sender := &RealEmailSender{} mux := setupRoutes(sender) log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", mux)) }
// RealEmailSender sends emails via SMTP (simplified for demo). type RealEmailSender struct{}
func (s *RealEmailSender) Send(to, subject, body string) error { // Real implementation would connect to SMTP fmt.Printf("Sending email to %s: %s\n", to, subject) return nil }
func setupRoutes(sender *RealEmailSender) *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("GET /api/users/{id}", handleGetUser) mux.HandleFunc("POST /api/notifications", handleCreateNotification(sender)) mux.HandleFunc("GET /api/notifications", handleListNotifications) return mux }
func handleGetUser(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var uid int fmt.Sscanf(id, "%d", &uid) user, ok := users[uid] if !ok { writeError(w, http.StatusNotFound, "user not found") return } writeJSON(w, http.StatusOK, map[string]any{"data": user}) }
func handleCreateNotification(sender *RealEmailSender) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var body struct {
UserID int json:"user_id"
Message string json:"message"
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
if body.UserID == 0 || body.Message == "" {
writeError(w, http.StatusBadRequest, "user_id and message are required")
return
}
user, ok := users[body.UserID]
if !ok {
writeError(w, http.StatusNotFound, "user not found")
return
}
err := sender.Send(user.Email, "Notification", body.Message)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to send notification")
return
}
notifMu.Lock()
notif := Notification{ID: nextNotifID, UserID: body.UserID, Message: body.Message, Status: "sent"}
notifications = append(notifications, notif)
nextNotifID++
notifMu.Unlock()
writeJSON(w, http.StatusCreated, map[string]any{"data": notif})
}}
func handleListNotifications(w http.ResponseWriter, r *http.Request) { notifMu.Lock() defer notifMu.Unlock() list := notifications if list == nil { list = []Notification{} } writeJSON(w, http.StatusOK, map[string]any{"data": list}) }
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}}) }