# Anatomy of a test

Jonathan Frere's complaint is that testing advice tends to be either abstract jargon or toy examples that look nothing like production tests. So he takes one ordinary test from a side project and explains every choice in it. The project is a bookmark app in Gleam that scrapes, tags, indexes and archives a saved link through an asynchronous job, and the test checks that `store.start_job` marks a job running and removes it from the pending list:

```gleam
pub fn start_job_marks_running_test() {
  use conn, Deps(clock:, ..) <- with_test_conn()

  mock_clock.set(clock, ts("2026-01-05T00:05:10Z"))
  let assert Ok(bookmark) = store.add_bookmark(conn, "http://example.com")
  let assert Ok(job) = store.schedule_job(conn, bookmark)

  let started_at = ts("2026-01-05T00:06:00Z")
  mock_clock.set(clock, started_at)

  let assert Ok(option.Some(started)) = store.start_job(conn, job)
  // ... field assertions ...
  store.list_pending_jobs(conn) |> should.equal(Ok([]))
}
```

He doesn't claim it is a model test, and the dissection finds things he'd change.

## Names and places

Gleam doesn't allow tests next to the file under test, which Frere would prefer, so `test/` mirrors `src/`, and each module usually exports one thing worth testing and gets one test file. He'd rather write test names as strings, as in JavaScript's `it("marks a job as running")` or Kotlin's backtick function names, because `start_job_marks_running_test` uses underscores both inside an identifier and as spaces. He admits he often names a test after writing it. What matters is that the name records the goal, which should outlast changes to the test body.

## The test constructor

`with_test_conn` opens an in-memory SQLite database, loads `db/schema.sql`, creates a mock clock, builds the store, and hands both the store and its dependencies to the test, with teardown handled by Gleam's `use`. The reasoning, which he thinks came from a passing remark by matklad: production code calls a function in several ways, so its signature has to be flexible, but tests call it the same way every time, so a test-only wrapper should absorb the boilerplate.

He contrasts it with the common `beforeEach` pattern of module-level variables and mock resets, adapted from a top search result for Jest mocks. It works, but the moving parts are scattered across the file, and the same amount of global mutation in non-test code would get flagged in review.

The database is real. Mocking a unique-index violation means asserting against a fiction with no guaranteed relationship to what the database does; he wants to test code and database as one unit, because that is what runs. Here it is cheap because production also uses SQLite. [[delightful-integration-tests-rust]] pays more for the same principle, starting real containers per test and binding their lifetime to the test's scope, and [[t-context-go-testing]] makes the matching Golang point that a test-owned resource should share the test's lifetime.

## Arrange through the public API

The mock clock exists because Gleam has no built-in way to fake global time. Frere prefers controlling time outright over approximate comparisons or skipping timestamp assertions, partly for flakiness and partly because time zones and leap days deserve their own cases.

Setup goes through `store.add_bookmark` and `store.schedule_job`, not raw `INSERT` statements, even though the constructor returns the database handle. `store.add_bookmark(...)` reads better. More importantly, a module's public API should change much less often than its internals. A test is itself a consumer of the interface, and a test that reaches beneath the abstraction breaks on refactors that leave behaviour intact. His corollary is the most portable idea in the post: if a test can't be written without bypassing the abstraction, it is probably hanging off the wrong layer, and belongs either higher up or deeper down. He'll go beneath the API in the arrange step only as a last resort, for example to create corrupted data the API exists to prevent.

## Act and assert

He'd make the act line stand out more, since it is where a reader looks first. He is less happy with the four field-by-field assertions and would now compare the whole value:

```gleam
started |> should.equal(store.Job(..job, status: store.Running(started_at:)))
```

That says exactly what is expected (the original job with a new status) and is future-proof. A new field that `start_job` leaves alone needs no test change, and one it modifies fails the test until the change is stated. Snapshot testing would make writing this easier, at the cost of blurring which attributes the assertion actually cares about.

The last line checks that the job left the pending list by calling another public function, not by querying the table. Frere is stricter about staying on the API in assertions than in setup.

## What one test is for

Tests serve two purposes for Frere: feedback while writing the code, faster than wiring up a CLI or endpoint, and a statement of behaviour that survives reimplementation, so the job queue could change without this test changing. One test doesn't cover `start_job`. Multiple jobs, deleted jobs, and already-started or finished jobs are left to other tests.
