05 — Errors, tests, and debugging
Goal
Return errors instead of hiding failures, and write your first automated Go tests.
Step 1: Test a domain rule
Create internal/tasks/task_test.go:
package tasks
import "testing"
func TestTaskIsComplete(t *testing.T) {
task := Task{ID: "task-1", Title: "Read", Status: StatusDone}
if !task.IsComplete() {
t.Fatal("IsComplete() = false, want true")
}
}
Run from server:
go test ./...
Go discovers files ending in _test.go. t.Fatal stops the current test; t.Errorf records a failure and continues.
Step 2: Compare sentinel errors
In a repository test, call Get for a missing ID and compare using errors.Is(err, ErrNotFound). This remains reliable if the error is wrapped with context.
An error value is ordinary data that describes a failure. A sentinel such as ErrNotFound gives code a stable condition to check. Wrapping adds context while preserving the original cause. At the boundary between the domain and gRPC, the handler converts these Go errors into gRPC status codes; internal details stay in server logs instead of leaking to the mobile user.
Step 3: Use the debugger's first tools
Add temporary fmt.Printf output at a boundary, run one test with go test -run TestTaskIsComplete -v, and remove the print after understanding the result. Use go vet ./... for suspicious constructs and go test -race ./... when code can be accessed concurrently.
Tests are executable examples of expected behavior. A unit test calls one small piece directly, so a failure points close to its cause. Integration tests exercise boundaries such as SQL or gRPC. The race detector instruments a run to find unsynchronized memory access; it is especially useful for maps or shared state used by simultaneous RPCs.
Step 4: Keep errors useful
Wrap an error with context using fmt.Errorf("get task %q: %w", id, err). %w preserves the cause so errors.Is and errors.As can inspect it. Return errors to callers; only the outermost command or server boundary decides how to log or translate them.
Checkpoint
Add a test that saves a task and reads it back. Add another test that confirms a missing task returns ErrNotFound.
Troubleshooting
undefined: Task: ensure the test usespackage tasks, is in thetasksdirectory, and the type is exported or the test is in the same package.- Test not found: check the function begins with
Testand accepts*testing.T.