Recipes

Wait for lifecycle events in tests

Replace fixed delays with an observer that completes when a semantic lifecycle condition becomes true.

v0.1.0-dev.1Intermediateusecase_forge_test
Open source reference

Wait for lifecycle events in tests

A fixed delay proves only that the machine waited. It does not identify the event that makes an assertion safe, and it becomes flaky when scheduling changes.

Name the terminal condition

test/counter_test.dart
useCaseTest<CounterUseCase, CounterState>(
  'increment publishes and completes',
  build: CounterUseCase.new,
  act: (useCase) => useCase.add(const IncrementCommand()),
  waitFor: (useCase, observer) =>
      observer.waitForHistory(hasLength(1)),
  expectedHistory: () => <Object?>[
    isTerminalUseCaseExecutionEntry<IncrementCommand>(
      result: UseCaseExecutionResult.completed,
    ),
  ],
);

The scenario continues when one terminal history entry exists. It does not assume a duration or infer how many microtasks the handler needs.

Use an observer for multi-command scenarios

test/orders_test.dart
final observer = UseCaseTestObserver<OrderState>(useCase);
addTearDown(() async {
  await useCase.close();
  await observer.cancel();
});

useCase
  ..add(const LoadOrder('a'))
  ..add(const LoadOrder('b'));

await observer.waitForHistory(hasLength(2));

The observer is read-only. Cancelling it stops the test subscription; it does not close or cancel the UseCase.

Control time-based policies

Use TestUseCaseClock for debounce, throttle, rate limits, and lifecycle timestamps. Advance the clock to the policy boundary, then wait for the semantic event. This separates simulated time from completion and removes machine-speed assumptions.

ARKTELOS

Purpose-built engineering systems for software that has to hold.