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 uses package tasks, is in the tasks directory, and the type is exported or the test is in the same package.
  • Test not found: check the function begins with Test and accepts *testing.T.

results matching ""

    No results matching ""