blooming_test 0.1.3
blooming_test: ^0.1.3 copied to clipboard
Comprehensive declarative testing harness and custom LPDAG matchers for the Bloom reactive framework.
blooming_test #
A comprehensive, declarative testing harness and LPDAG state matcher library for the blooming reactive state framework.
Features #
- Declarative Test Runner (
bloomTest<T>): Test reactive state transitions, mutations, and derived pipelines with a structured, step-by-step API (build,target,act,wait,expect,verify,tearDown). - Deterministic Virtual Time (
FakeAsync): Effortlessly fast-forward debounce, throttle, interval, and time-based graceful mooring windows with zero wall-clock latency. - LPDAG Custom Matchers:
isDormant/isActive: Verify zero-leak subscriber-driven dormant lifecycle states.hasSubscribers(n): Interrogate active subscriber count in the reactive graph.hasGlitchFreedom: Prove absence of diamond-dependency intermediate glitch states.isData(expected)/isEmptyState(reason)/isErrorState(error): Deep-match 3-StateBloomSnapshotcontainers.
- Pure Dart: Zero Flutter SDK dependency; runs seamlessly in Server, CLI, VM, and Web test environments.
Installation #
Add blooming_test to your dev_dependencies in pubspec.yaml:
dev_dependencies:
test: ^1.25.0
blooming_test: ^0.1.0
Or run:
dart pub add dev:blooming_test
Practical Examples & Usage #
Example 1: Testing a Business Logic Store (bloomTest) #
Test state mutations and derived computation pipelines using a declarative lifecycle:
import 'package:test/test.dart';
import 'package:blooming/blooming.dart';
import 'package:blooming_test/blooming_test.dart';
/// Shopping cart business logic store
class CartStore {
final items = BloomController<List<String>>([]);
/// Derived reactive count: auto-computed via BloomVia
late final Bloom<int> itemCount = computed((via) => via(items.bloom).length);
void addItem(String item) {
items.data = [...items.data, item];
}
void removeItem(String item) {
items.data = items.data.where((i) => i != item).toList();
}
}
void main() {
group('CartStore Tests', () {
bloomTest<CartStore>(
'tracks item additions, removals, and updates derived itemCount',
build: () => CartStore(),
target: (store) => store.itemCount,
act: (store) {
store.addItem('MacBook Pro');
store.addItem('Magic Mouse');
store.removeItem('Magic Mouse');
},
// Target emits distinct states: initial 0 -> 1 -> 2 -> 1
expect: () => [0, 1, 2, 1],
verify: (store) {
expect(store.items.data, ['MacBook Pro']);
expect(untracked(store.itemCount), 1);
},
);
});
}
Example 2: Testing Debounce & Temporal Pipelines with Virtual Time (wait) #
When testing query.bloom.debounced(...), you do not need real Future.delayed delays. Pass wait: Duration(...) to advance virtual time instantaneously with built-in FakeAsync:
class SearchStore {
SearchStore() {
debouncedQuery = query.bloom.debounced(const Duration(milliseconds: 300));
}
final query = BloomController('');
late final Bloom<String> debouncedQuery;
}
void main() {
group('SearchStore Debounce Test', () {
bloomTest<SearchStore>(
'suppresses rapid keystrokes and only emits the last value after 300ms',
build: () => SearchStore(),
target: (store) => store.debouncedQuery,
act: (store) {
// User types rapidly within 300ms
store.query.data = 'd';
store.query.data = 'da';
store.query.data = 'dart';
},
// Fast-forward 300ms virtual clock instantly
wait: const Duration(milliseconds: 300),
// Intermediate 'd' and 'da' are collapsed; only initial '' and 'dart' are emitted
expect: () => ['', 'dart'],
verify: (store) {
expect(store.query.data, 'dart');
},
);
});
}
Example 3: Verifying LPDAG Inactive Dormancy (isDormant, isActive) #
Verify the zero-leak Subscriber-Driven Inactive Dormancy invariant. When an effect runner or UI disconnects, nodes must automatically return to dormancy:
test('proves derived node enters zero-leak dormancy when unobserved', () {
final count = BloomController(10);
final doubled = computed((via) => via(count.bloom) * 2);
// 1. Initially dormant: 0 active subscribers
expect(doubled, isDormant);
expect(count, isDormant);
// 2. Active observation wakes up the node
final runner = doubled.effect(() {}).run();
expect(doubled, isActive);
expect(doubled, hasSubscribers(1));
// 3. Deactivating the runner returns the graph to dormant state
runner.deactivate();
expect(doubled, isDormant);
});
Example 4: Testing 3-State FSM Snapshots (isData, isEmptyState, isErrorState) #
Assert that nodes or controllers transition cleanly between Data, Empty, and Error states:
test('verifies 3-State FSM transitions', () {
// 1. Initial Empty state with descriptive reason
final user = BloomController<String>.late(reason: 'AwaitingAuth');
expect(user, isEmptyState('AwaitingAuth'));
// 2. Transition to valid Data state
user.data = 'Alice';
expect(user, isData('Alice'));
// 3. Transition to Error state
user.setError(Exception('Session expired'));
expect(user, isErrorState());
});
Complete Executable Example #
Check out the full, runnable showcase at example/blooming_test_example.dart. Run it directly via:
dart test example/blooming_test_example.dart
License #
Apache License 2.0. Copyright 2023-2026 Youngjune Jeon.