Recipes
Wait for lifecycle events in tests
Replace fixed delays with an observer that completes when a semantic lifecycle condition becomes true.
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
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
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.