CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl-labs/go-api-testing

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

1.06x
Quality

98%

Does it follow best practices?

Impact

99%

1.06x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

task.mdevals/scenario-2/

Write Tests Using Interface-Based Dependency Injection for a Go Notification Service

Problem/Feature Description

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:

  1. Define an interface for the email sender dependency
  2. Create a mock implementation in the test file
  3. Write tests that inject the mock to test both success and failure scenarios (email service down returns 503, not 500)

Output Specification

Produce the following files:

  • handlers.go -- refactored handlers that accept the EmailSender interface
  • handlers_test.go -- tests with mock EmailSender implementation
  • testhelpers_test.go -- reusable test helpers

The interface and mock should be simple -- a hand-written struct, not a code-generated mock from gomock or mockery.

Input Files

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}}) }

evals

tile.json