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 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/testify — assert/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:
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 in | internal/**/*_test.go | features/, internal/gandalf |
| Run by | go test ./internal/... | local/run.bash bdd (godog) |
| Needs | nothing — pure mocks | the full Docker stack + real DBs |
| Speed | milliseconds | seconds–minutes |
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.
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?
go test.
Anything touching a real DB is integration/E2E — out of scope.
Q2. Which mocking library does this repo use for unit tests?
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?
*_test.go sits in the same directory
and package as the implementation.
*_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.go test. Anything that needs real infrastructure I'd call integration or end-to-end,
and I'd run it separately."
1. Go testing,
testify. In-repo map:
docs/testing/reference/repo-testing-map.md;
scope boundary local/run.bash:219-246.