Lesson 1 · The unit-test foundation

What a unit test is here

One unit, in isolation, with every collaborator faked — no database, no network, no Docker. And a firm line around what this course covers.

Your win: define what counts as a unit test in this repo, name the four tools every one uses, and draw the line between the unit world (this course) and the BDD/E2E world (not this course).

What a unit test is — and isn't

A unit test exercises one unit — a function or method — in isolation. Its collaborators (repositories, the database, other services) are replaced by mocks, so the test runs in milliseconds, deterministically, with nothing external.1 In this repo that means a *_test.go file, sitting next to the code it tests (same package), run by go test.

The defining property No real infrastructure. A unit test never opens a Postgres connection, never dials a service, never needs the Docker stack. If a test touches a real database, it isn't a unit test — it's integration or E2E, and it's out of scope for this course. That single line decides what we teach.

The four tools

Every unit test here is built from the same small toolkit:

1 · go test

Go's built-in runner. Finds every TestXxx(t *testing.T) in *_test.go and runs it. The whole thing is standard-library.

2 · testify

stretchr/testifyassert/require for checks and mock for test doubles. 100% of this repo's mocking is testify — no gomock anywhere.

3 · mock/

A tree of generated mocks mirroring internal/. You inject these into the code under test — never hand-write one.

4 · testutil.MockDB

A house toolkit (mock/testutil/) that fakes the Postgres driver — so even a repository can be tested without a database.

The scale tells you how settled these conventions are: roughly 2,778 test files under internal/, 1,807 importing testify/mock, and 2,377 calling t.Parallel(). There is one house style, and you're about to learn it.

The loop: write → mock → generate → run → cover

This is the shape of the whole course. Every lesson is a stop on it:

WRITE a table-driven test (Part 1): subtests, t.Parallel, arrange/act/assert │ MOCK inject a testify mock for each collaborator, program it (Part 2) │ GENERATE regenerate mocks from interfaces with `make gen-mock` (Part 2) │ RUN go test ./internal/spike/... -run ^TestX$ (Part 3) │ COVER make service=spike unit-test-service → coverage % (Part 3)

The scope line — where this course stops

The repo has a whole second kind of test that we will name and then leave alone:

Unit tests (this course)BDD / E2E (out of scope)
Lives ininternal/**/​*_test.gofeatures/, internal/gandalf
Run bygo test ./internal/...local/run.bash bdd (godog)
Needsnothing — pure mocksthe full Docker stack + real DBs
Speedmillisecondsseconds–minutes
Why the boundary matters for learning Mixing the two is the most common source of confusion. When you open a test, the first question is "unit or E2E?" — because the tools, the speed, and the mental model are completely different. This course lives entirely on the left column. Whenever a lesson says "the database," it means a mock of the database, never a real one.
Your anchor: spike Every lesson leans on spike (internal/spike/modules/email/) — your own service, and the cleanest example of the house style, with real unit tests at every layer (repository, usecase, gRPC handler). Keep internal/spike/modules/email/application/commands/create_email_handler_test.go open; it's the file we'll return to most.
Read this next

The Go testing package & your first test

The standard-library foundation everything here sits on — the test function shape and how go test finds and runs it.

pkg.go.dev — testing · go.dev — Add a test
testify (the assert + mock toolkit) · in-repo .claude/rules/go-test-style.md

Check yourself (from memory)

Q1. What makes a test a unit test in this repo?

Isolation via mocks, no infrastructure, run by go test. Anything touching a real DB is integration/E2E — out of scope.

Q2. Which mocking library does this repo use for unit tests?

1,807 files import testify/mock; zero use gomock. (One header says mockgen but it's misleading — Lesson 7.)

Q3. Where does a unit test file live relative to the code it tests?

Go convention: *_test.go sits in the same directory and package as the implementation.
Recall: what a unit test is here + the four tools + the scope line.
define + list + the boundary, then reveal
Unit test: one function/method in isolation, collaborators faked with mocks, no real DB/network/Docker; a *_test.go next to the code (same package), run by go test. Four tools: go test (runner) · testify (assert/require + mock, 100% — no gomock) · mock/ (generated mocks mirroring internal/) · testutil.MockDB (fakes the pgx driver). The loop: write → mock → generate → run → cover. Scope line (EXCLUDED): features/, internal/gandalf, local/run.bash bdd (godog + Docker + real DB), any real-DB test = BDD/E2E, not this course. Anchor: spike.
🎯 Interview one-liner "What's a unit test to you?" → "One unit in isolation — collaborators replaced by mocks, no real database or network, so it's fast and deterministic. It lives next to the code and runs under go test. Anything that needs real infrastructure I'd call integration or end-to-end, and I'd run it separately."
Next lesson: the shape every test here takes — table-driven tests and subtests, with the one parallelism pitfall that bites everyone. Unsure whether something is a unit or E2E test? Ask me — that's what I'm here for.

1. Go testing, testify. In-repo map: docs/testing/reference/repo-testing-map.md; scope boundary local/run.bash:219-246.