Some things cannot be mocked.
A humanity-check widget either exists on a real hostname or it does not. Mail is either delivered or it is not. A certificate is either issued or it is not.
The suites that prove those need a fixture: a real thing, made once, in a console, by a human.
Absent means skip, never fail
Testing your backend is the wider practice, local development is where fixtures usually run, and contributing is where this rule is enforced on the kit itself.
You do not need any of this to work on the kit. A checkout with no credentials runs the tests and gets green — the live suites skip, and the run says which ones and why.
fixture absent: turnstile-widget. TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY are not set.
Turnstile sign-in gating on a real hostname skips.
fixture present: cloudflare-account.
fixtures: 1 present, 7 absent, 0 malformed, 0 declined. A skipped suite is not a passing one.That last sentence is the whole point of the report. A skipped suite reads as a passing one in every summary line a test runner prints, and the only defense is saying so out loud.
Where the report runs, and why it matters
Once per run, before a single suite is collected — rather than in a suite hook.
The reason is mechanical: a test runner runs no hooks inside a skipped block, so anything reporting from inside one is gated on exactly the condition it exists to report. A fixture-absence notice that only prints when the fixture is present is not a notice.
Four outcomes, and three of them skip
| Outcome | What it means |
|---|---|
absent | No variable is set. The normal case. Nobody did anything wrong |
declined | A switch is explicitly off. Deliberate |
malformed | Somebody set it and it will not do — empty, whitespace, or the literal text undefined |
present | The suite runs |
malformed is the one worth separating out. An empty string and an unset variable both skip, and they mean completely different things: one is nobody configured this and the other is somebody tried and it did not take. Collapsing them loses the second entirely.
Two entry points, chosen by what the suites create
One reports and also sweeps stale resources — the right one for a package whose tests mint a Worker, a database or a bucket.
One only reports, for a package whose live suites merely read a third party and so have nothing to reclaim. Sweeping the whole account on their way past would be somebody else’s housekeeping, done at a surprising moment.
Switch a config from the second to the first the moment one of its suites creates a resource.
Applying this to your own project
The pattern transfers, and it is worth copying.
Report once, before collection. Not in a hook inside the thing being skipped.
Say what skipped and why, in one line each, naming the variables and what they would have exercised.
Say the total out loud, including the sentence that a skipped suite is not a passing one.
Separate malformed from absent. They are different mistakes.
Never fail on absence. A contributor with no credentials should get a green run — otherwise the first thing anybody does is set the variables to nonsense to make the noise stop, which is strictly worse.
What a fixture entry should name
Four things, and the fourth is the one people leave out:
- The fixture, by name
- The variables that address it
- What skips without it
- How to make it — the console, the page, the steps
Without the fourth, a fixture report is a list of things somebody else set up once.
Live suites are not a substitute for the ordinary ones
The kit’s testing argument is that Workers tests run inside the real runtime against real D1 and KV through the local emulator — so most things need no fixture at all.
Live fixtures are for the handful of cases where the third party is the thing under test: a real widget on a real hostname, a real message actually delivered, a real certificate actually issued.
If you find yourself reaching for a live fixture to test your own logic, the test probably wants the local path instead.