Testing
This content is for the 0.1 version. Switch to the latest version for up-to-date documentation.
Everything above the transport can be tested without a network, because the transport is an interface and one implementation is a connected pair of streams.
A fake host
Section titled “A fake host”final (clientSide, serverSide) = InMemoryTransport.pair();final client = AhpConnection(clientSide);
serverSide.incoming.listen((message) { // Answer as a host would.});pair() returns two transports that deliver to each other. Everything above —
JSON-RPC framing, reducers, the mirror, reconciliation — runs exactly as it does
against a real host, because none of it can tell the difference.
Freezing the clock
Section titled “Freezing the clock”Reducers stamp modifiedAt, so they read a clock. Inject it rather than letting
them reach for DateTime.now():
final mirror = AhpStateMirror( clientId: 'test', now: () => DateTime.fromMillisecondsSinceEpoch(9999),);The fixture corpora depend on this — they assert exact timestamps, and 9999ms is the value upstream froze them at.
Unlike Go’s process-global SetNowProvider, the seam here is a constructor
parameter, because Dart tests share an isolate and a global would leak between
them.
Reducers directly
Section titled “Reducers directly”Reducers are pure functions. Most of the time you do not need a transport at all:
final next = chatReducer(state, action, clientId: 'test', now: frozen);expect(next.activeTurn?.responseParts, hasLength(2));This is how the 246 upstream fixtures run, and it is the fastest way to pin down behaviour.
Two traps in widget tests
Section titled “Two traps in widget tests”Linger schedules a timer
Section titled “Linger schedules a timer”Releasing the last lease does not unsubscribe immediately — the runtime holds it
for five seconds so navigating away and back is cheap. In a widget test that
timer is still pending when the test ends, and flutter_test fails on it.
AhpRuntime( connect: connect, clientId: 'test', lingerDuration: Duration.zero,);Zero means teardown runs inline, with no timer to leave pending.
Attach the listener before initializing
Section titled “Attach the listener before initializing”final actions = client.actions.listen(mirror.applyEnvelope);await client.initialize(params); // ← in this orderactions is a broadcast stream. Reversing these lines loses every envelope sent
during the handshake, and nothing reports it — the test just sees state that
disagrees with what the fake host sent, some distance from the cause.
Running the suite
Section titled “Running the suite”dart test416 tests: 246 reducer fixtures, 39 round-trip fixtures, and the rest covering reconciliation, the runtime, and the transport.
The two fixture harnesses read the vendored corpora under upstream/ and
hard-fail if they cannot find them. That guard is deliberate — a fixture suite
that silently passes because it found no fixtures is worse than one that fails.
It also means those two cannot run from a published archive, which is why
test/ is not shipped.