bloc_one_shot_test 0.1.0
bloc_one_shot_test: ^0.1.0 copied to clipboard
Test utilities for bloc_one_shot. Provides blocEffectTest() helper that mirrors blocTest() API with added effect verification.
bloc_one_shot_test #
Test utilities for bloc_one_shot. Provides a blocEffectTest helper that mirrors the blocTest API with added effect verification.
Installation #
dev_dependencies:
bloc_one_shot_test: ^0.1.0
Usage #
blocEffectTest #
A test helper for Blocs and Cubits that use SideEffectMixin. It verifies both state emissions and side effect emissions in a single test.
import 'package:bloc_one_shot_test/bloc_one_shot_test.dart';
blocEffectTest<LoginCubit, LoginState, LoginEffect>(
'emits [Loading, Success] and NavigateToHome on login',
build: () => LoginCubit(authRepo: mockAuthRepo),
act: (cubit) => cubit.login('user@example.com', 'password'),
expect: () => [
isA<LoginLoading>(),
isA<LoginSuccess>(),
],
expectEffects: () => [
isA<NavigateToHome>(),
],
);
Parameters #
| Parameter | Type | Required | Description |
|---|---|---|---|
description |
String |
Yes | Test description |
build |
B Function() |
Yes | Factory that creates the Bloc/Cubit under test |
act |
FutureOr<void> Function(B)? |
No | Interaction with the Bloc/Cubit (add events, call methods) |
wait |
Duration? |
No | Additional wait time for async operations |
skipStates |
int |
No | Number of initial state emissions to skip (default: 0) |
skipEffects |
int |
No | Number of initial effect emissions to skip (default: 0) |
expect |
dynamic Function()? |
No | Expected states — same as blocTest |
expectEffects |
dynamic Function()? |
No | Expected side effects |
verify |
FutureOr<void> Function(B)? |
No | Additional verification after the test |
setUp |
FutureOr<void> Function()? |
No | Setup callback before the test |
tearDown |
FutureOr<void> Function()? |
No | Teardown callback after the test |
Examples #
Verify only effects (no state change)
blocEffectTest<AuthCubit, AuthState, AuthEffect>(
'emits ShowErrorSnackbar when login fails',
build: () => AuthCubit(authRepo: failingMockRepo),
act: (cubit) => cubit.login('bad@email.com', 'wrong'),
expect: () => [isA<AuthLoading>(), isA<AuthInitial>()],
expectEffects: () => [isA<ShowErrorSnackbar>()],
);
Verify no effects were emitted
blocEffectTest<CounterCubit, int, CounterEffect>(
'does not emit effects on normal increment',
build: () => CounterCubit(),
act: (cubit) => cubit.increment(),
expect: () => [1],
expectEffects: () => <CounterEffect>[],
);
Skip initial emissions
blocEffectTest<CounterBloc, int, CounterEffect>(
'only checks the last state after 3 increments',
build: () => CounterBloc(),
act: (bloc) {
bloc.add(Increment());
bloc.add(Increment());
bloc.add(Increment());
},
wait: const Duration(milliseconds: 50),
skipStates: 2,
expect: () => [3],
);
With setUp, tearDown, and verify
blocEffectTest<AuthCubit, AuthState, AuthEffect>(
'navigates to home on successful login',
setUp: () {
// Prepare mocks, test fixtures, etc.
when(() => mockRepo.login(any(), any())).thenAnswer((_) async {});
},
build: () => AuthCubit(authRepo: mockRepo),
act: (cubit) => cubit.login('test@test.com', 'password'),
expectEffects: () => [isA<NavigateToHome>()],
verify: (cubit) {
verify(() => mockRepo.login('test@test.com', 'password')).called(1);
},
tearDown: () {
// Clean up resources
},
);
Works with both Bloc and Cubit
// Bloc
blocEffectTest<LoginBloc, LoginState, LoginEffect>(
'Bloc: emits NavigateToHome',
build: () => LoginBloc(),
act: (bloc) => bloc.add(LoginRequested(email: 'a', password: 'b')),
expectEffects: () => [isA<NavigateToHome>()],
);
// Cubit
blocEffectTest<LoginCubit, LoginState, LoginEffect>(
'Cubit: emits NavigateToHome',
build: () => LoginCubit(),
act: (cubit) => cubit.login('a', 'b'),
expectEffects: () => [isA<NavigateToHome>()],
);
Async operations with wait
For Blocs with async event handlers, use wait to allow time for operations to complete:
blocEffectTest<AuthBloc, AuthState, AuthEffect>(
'handles async login with network delay',
build: () => AuthBloc(authRepo: slowMockRepo),
act: (bloc) => bloc.add(LoginRequested(email: 'a', password: 'b')),
wait: const Duration(milliseconds: 300),
expect: () => [isA<AuthLoading>(), isA<AuthSuccess>()],
expectEffects: () => [isA<NavigateToHome>()],
);
How It Works #
blocEffectTest creates a test that:
- Calls
setUp(if provided) - Builds the Bloc/Cubit via
build() - Subscribes to both
bloc.stream(states) andbloc.effects(side effects) - Calls
actto interact with the Bloc/Cubit - Waits for the specified
waitduration (or two microtask cycles by default) - Asserts collected states against
expect - Asserts collected effects against
expectEffects - Calls
verifyfor additional assertions - Closes the Bloc/Cubit
- Calls
tearDown(if provided)
Comparison with blocTest #
| Feature | blocTest |
blocEffectTest |
|---|---|---|
| Verify states | expect |
expect |
| Verify effects | N/A | expectEffects |
| Skip states | skip |
skipStates |
| Skip effects | N/A | skipEffects |
| setUp / tearDown | Yes | Yes |
| verify | Yes | Yes |
| wait | Yes | Yes |
License #
MIT — see LICENSE for details.