|
โก bloc_signals_test"With the rigor of BLoC and the flex and speed of Signals"
Declarative unit testing utilities for bloc_signals
and |
bloc_signals_test provides blocSignalTest, a declarative helper tailored specifically for synchronous reactive signal state propagation, state de-duplication, and observer isolation.
๐ Ecosystem Packages
The BlocSignal monorepo consists of 11 modular packages:
โก Key Features
- ๐ฏ Declarative Assertions: Verify emitted states in exact order using
expect. - โฑ๏ธ Async Support: Await asynchronous event handlers or timers using
wait. - โญ๏ธ State Skipping: Skip initial emissions using
skip. - ๐จ Error Testing: Verify exceptions reported to
onError, rethrownErrorsubtypes (for exampleStateErrororArgumentError), and directCubitSignalmethod throws usingerrors. - ๐งน Automatic Cleanup: Guarantees observer restoration and
bloc.close()post-test.
๐ Getting Started
Add bloc_signals_test to your pubspec.yaml:
dev_dependencies:
bloc_signals_test: ^1.0.0
test: ^1.24.0
๐ก Quick Examples
1. Cubit Unit Test (blocSignalTest)
import 'package:bloc_signals_test/bloc_signals_test.dart';
import 'package:test/test.dart';
void main() {
group('CounterCubit', () {
blocSignalTest<CounterCubit, int>(
'emits [1] when increment is called',
build: CounterCubit.new,
act: (cubit) => cubit.increment(),
expect: () => [1],
);
});
}
2. Async Event Bloc Test (wait & errors)
blocSignalTest<DataBloc, DataState>(
'emits [DataLoading, DataLoaded] when FetchData succeeds',
build: () => DataBloc(repository: mockRepo),
act: (bloc) => bloc.add(FetchData()),
wait: const Duration(milliseconds: 100),
expect: () => [
const DataLoading(),
const DataLoaded('sample_data'),
],
);
3. Mocking & Faking for Widget & Golden Tests
In BlocSignal, cubit.state is a reactive ReadonlySignal<S> (which BlocSignalBuilder, BlocSignalListener, BlocSignalSelector, and context.select subscribe to inside reactive effects and computeds), while cubit.value (and cubit.stateValue) returns the raw S snapshot. Attempting to stub when(() => cubit.state).thenReturn(MyLoadedState()) with mocktail will fail because MyLoadedState is not a ReadonlySignal<MyState>.
Idiomatic Fake (Recommended)
Because CubitSignal and BlocSignal are streamless and propagate state synchronously in the same frame, the cleanest pattern for widget and golden tests is either seeding a real container via initialState: or creating a lightweight Fake class:
class FakeUserCubit extends CubitSignal<UserState> implements UserCubit {
FakeUserCubit({super.initialState = const UserInitial()});
void emitState(UserState newState) => emit(newState);
@override
Future<void> refresh() async {}
}
// In testWidgets:
final fakeCubit = FakeUserCubit(initialState: const UserLoading());
addTearDown(fakeCubit.close);
await tester.pumpWidget(
MaterialApp(
home: BlocSignalProvider<UserCubit>.value(
value: fakeCubit,
child: const UserProfileView(),
),
),
);
fakeCubit.emitState(const UserLoaded('Alice'));
await tester.pump();
Stubbing with mocktail and a Backing signal<S>()
When you need mocktail verification (for example, verify(() => mockCubit.refresh()).called(1)), back mockCubit.state with a real signal<S>(), register a fallback state value for equals(any(), any()), and dispose the backing signal in close() via addTearDown(mockCubit.close):
class MockUserCubit extends Mock implements UserCubit {}
// In main():
setUpAll(() => registerFallbackValue(const UserInitial()));
// In testWidgets:
final mockCubit = MockUserCubit();
final stateSignal = signal<UserState>(const UserInitial());
addTearDown(mockCubit.close);
when(() => mockCubit.state).thenReturn(stateSignal);
when(() => mockCubit.value).thenAnswer((_) => stateSignal.value);
when(() => mockCubit.stateValue).thenAnswer((_) => stateSignal.value);
when(() => mockCubit.equals(any(), any())).thenAnswer(
(inv) => inv.positionalArguments[0] == inv.positionalArguments[1],
);
when(() => mockCubit.isClosed).thenReturn(false);
when(() => mockCubit.close()).thenAnswer((_) async => stateSignal.dispose());
// Trigger reactive widget rebuilds by mutating stateSignal.value:
stateSignal.value = const UserLoaded('Alice');
await tester.pump();
๐ค AI Coding Assistant Skill
This package is supported by an official pre-packaged AI Coding Skill representing unit testing patterns, observer isolation, and synchronous assertion guidelines for BlocSignal.
If you develop with AI coding assistants (such as Claude Code, Antigravity, Gemini, Cursor, or Codex), you can load the bloc-signals skill bundle to guide your assistant's code generation and analysis.
๐ License
MIT License. See LICENSE for details.