tarek.kharsa

home / lessons / t3 code · lesson 3

TestingT3 Code · lesson 3 of 82 min read

Mock the boundary, not the logic

“A test that needs a timeout to pass is wrong.” Testing rules from a repo that bans sleeps.

Problem

Tests that mock your own logic, or sleep and hope, pass on your machine and flake or lie everywhere else.

Idea

Fake only the true boundaries (network, processes, clock, randomness) and wait for milestones instead of sleeping.

Try it: sleep vs a test clock

the same test, run 20 times on a noisy machine

A test that sleeps 100 ms and hopes the job finished fails whenever the machine is slow. A test that advances a fake clock and waits for an explicit milestone passes every time, and spends no time waiting.

One sentence in the agent guide sums up the testing philosophy: "A test that needs a timeout to pass is wrong." Here's how they make that achievable.

The default test shape

command dispatch
  → real orchestrator
  → real provider adapter
  → REPLAYED provider transport     ← the only fake
  → real normalizer
  → real event store + projections
  → assertions

Allowed fakes

True boundaries only: the external process, the network, the clock, random IDs, a temp filesystem, a temp database.

Not allowed

A mocked orchestrator, adapter, normalizer, event sink, projection reducer or checkpoint policy.

A few integration tests that run the real stack beat hundreds of unit tests that mock away the behavior they claim to test.

How they remove every reason to sleep

  1. Replay real trafficThe app talks to several AI agent CLIs. Tests replay recorded provider transcripts through the real adapter, so parsing, ordering and error paths are exercised for real.
  2. Make time a dependencyProduction code reads time through the framework's Clock and randomness through its Random service, never Date.now() or Math.random(). Tests swap in a TestClock and a seeded Random: no flaky timestamps, no snapshots that change because of a new UUID.
  3. Drain, don't sleepBackground work runs on a drainable worker whose drain() resolves only when the queue is empty and the item in progress has finished. An empty queue alone doesn't prove the worker is idle, and that gap is where most "add a 100 ms sleep" hacks come from.
  4. Restart the way production restartsTo test "the app restarted mid-task", they tear down the outermost server layer and rebuild it against the same durable storage. No restartForTest() method in production code.

An empty database is a bad test

One script rebuilds a dev database from a read-only, pruned snapshot of the developer's real data. It drops scheduled tasks, pending work and auth sessions, so the dev server never runs real agents. Then it runs migrations on top, which proves new migrations apply to real data and catches two branches claiming the same migration number.

And what not to test

  • Don't render components to static markup just to assert props, or write tests that only mirror the implementation.
  • Lint bans declaring tests inside a for loop; use it.each.
  • Locally, run the smallest proof that the change works. CI owns the full suite.

Steal this

"No mocks" is the wrong lesson. The rule is to fake only at true boundaries: process, network, clock, IDs, filesystem. Make time and randomness injectable in production code, and wait on milestones instead of sleeping.

Use it in your code

A prompt for your coding agent

Run it from the root of your repo. It checks whether the idea fits before it changes anything.

Review this codebase's tests against the rule "fake the boundaries, not the logic."

The idea: replace only true boundaries in tests (network, processes, clock, random IDs, filesystem) and run the real code everywhere else. Time and randomness should be injectable in production code, and tests should wait for milestones instead of sleeping.

1. Find tests that mock our own modules or business logic, and tests that sleep or use timeouts to wait for something. List the worst offenders with file and line.
2. Pick one module. Check whether its clock, random IDs and external calls can be injected. If not, propose the smallest way to make them injectable without changing behavior.
3. Rewrite one or two of its tests: fake only the boundary, and replace sleeps with waiting on an explicit signal (a promise, an event, a drained queue).
4. Run them several times to show they're stable.

Don't rewrite the whole suite. Keep the change small and tell me what you'd do next.

Read the source

Where this comes from

T3 Code (pingdotgg/t3code), the open-source GUI for coding agents. The files behind this lesson: