Set up proper test configuration for Cubits using bloc_superpowers with Superpowers.clear() and Completers
This skill sets up proper test configuration for Cubits using bloc_superpowers.
Configures tests to:
Always call Superpowers.clear() in setUp to reset state between tests:
import 'package:bloc_superpowers/bloc_superpowers.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
setUp(() {
Superpowers.clear(); // IMPORTANT: Reset state between tests
});
// ... tests
}
import 'package:bloc_superpowers/bloc_superpowers.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockApi extends Mock implements Api {}
void main() {
late UserCubit cubit;
late MockApi mockApi;
setUp(() {
Superpowers.clear(); // Reset Superpowers state
mockApi = MockApi();
cubit = UserCubit(api: mockApi);
});
tearDown(() {
cubit.close();
});
group('UserCubit', () {
test('initial state is correct', () {
expect(cubit.state, const UserState());
});
// ... more tests
});
}
Use Completer to control when async operations complete:
test('loadUser sets loading state while fetching', () async {
final completer = Completer<User>();
// Mock returns a future that we control
when(() => mockApi.getUser()).thenAnswer((_) => completer.future);
// Start the operation
cubit.loadUser();
// Wait for next event loop
await Future.delayed(Duration.zero);
// Check loading state
expect(Superpowers.isWaiting(UserCubit), isTrue);
// Complete the operation
completer.complete(User(name: 'John'));
// Wait for completion
await Future.delayed(Duration.zero);
// Check final state
expect(Superpowers.isWaiting(UserCubit), isFalse);
expect(cubit.state.user?.name, 'John');
});
test('isWaiting is true during load', () async {
final completer = Completer<User>();
when(() => mockApi.getUser()).thenAnswer((_) => completer.future);
cubit.loadUser();
await Future.delayed(Duration.zero);
// Use static method
expect(Superpowers.isWaiting(UserCubit), isTrue);
completer.complete(User(name: 'John'));
await Future.delayed(Duration.zero);
expect(Superpowers.isWaiting(UserCubit), isFalse);
});
test('per-item loading state', () async {
final completer = Completer<void>();
when(() => mockApi.deleteItem('123')).thenAnswer((_) => completer.future);
cubit.deleteItem('123');
await Future.delayed(Duration.zero);
// Check specific item key
expect(Superpowers.isWaiting((DeleteItem, '123')), isTrue);
expect(Superpowers.isWaiting((DeleteItem, '456')), isFalse);
completer.complete();
await Future.delayed(Duration.zero);
expect(Superpowers.isWaiting((DeleteItem, '123')), isFalse);
});
test('isFailed is true after error', () async {
when(() => mockApi.getUser()).thenThrow(Exception('Network error'));
cubit.loadUser();
await Future.delayed(Duration.zero);
expect(Superpowers.isFailed(UserCubit), isTrue);
expect(Superpowers.getException(UserCubit), isA<UserException>());
});
test('error clears on retry', () async {
// First call fails
when(() => mockApi.getUser()).thenThrow(Exception('Network error'));
cubit.loadUser();
await Future.delayed(Duration.zero);
expect(Superpowers.isFailed(UserCubit), isTrue);
// Second call succeeds
when(() => mockApi.getUser()).thenAnswer((_) async => User(name: 'John'));
cubit.loadUser();
await Future.delayed(Duration.zero);
expect(Superpowers.isFailed(UserCubit), isFalse);
expect(cubit.state.user?.name, 'John');
});
blocTest<UserCubit, UserState>(
'emits user when loadUser succeeds',
setUp: () {
Superpowers.clear(); // Reset in setUp
when(() => mockApi.getUser()).thenAnswer(
(_) async => User(name: 'John'),
);
},
build: () => UserCubit(api: mockApi),
act: (cubit) => cubit.loadUser(),
expect: () => [
UserState(user: User(name: 'John')),
],
);
test('effect is emitted on success', () async {
when(() => mockApi.saveUser(any())).thenAnswer((_) async {});
cubit.saveUser(User(name: 'John'));
await Future.delayed(Duration.zero);
// Check effect was emitted
expect(cubit.state.successMessageEffect.isNotSpent, isTrue);
// Consume the effect
final message = cubit.state.successMessageEffect.consume();
expect(message, 'User saved!');
// Effect is now spent
expect(cubit.state.successMessageEffect.isSpent, isTrue);
});
import 'package:bloc_superpowers/bloc_superpowers.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockApi extends Mock implements Api {}
void main() {
late ProductCubit cubit;
late MockApi mockApi;
setUp(() {
Superpowers.clear();
mockApi = MockApi();
cubit = ProductCubit(api: mockApi);
});
tearDown(() {
cubit.close();
});
group('loadProducts', () {
test('sets loading state while fetching', () async {
final completer = Completer<List<Product>>();
when(() => mockApi.getProducts()).thenAnswer((_) => completer.future);
cubit.loadProducts();
await Future.delayed(Duration.zero);
expect(Superpowers.isWaiting(ProductCubit), isTrue);
completer.complete([Product(id: '1', name: 'Widget')]);
await Future.delayed(Duration.zero);
expect(Superpowers.isWaiting(ProductCubit), isFalse);
expect(cubit.state.products.length, 1);
});
test('sets error state on failure', () async {
when(() => mockApi.getProducts()).thenThrow(Exception('Error'));
cubit.loadProducts();
await Future.delayed(Duration.zero);
expect(Superpowers.isFailed(ProductCubit), isTrue);
});
});
group('deleteProduct', () {
test('tracks loading per product', () async {
final completer = Completer<void>();
when(() => mockApi.deleteProduct('1')).thenAnswer((_) => completer.future);
cubit.deleteProduct('1');
await Future.delayed(Duration.zero);
expect(Superpowers.isWaiting((DeleteProduct, '1')), isTrue);
expect(Superpowers.isWaiting((DeleteProduct, '2')), isFalse);
completer.complete();
await Future.delayed(Duration.zero);
expect(Superpowers.isWaiting((DeleteProduct, '1')), isFalse);
});
});
}
Superpowers.clear() in setUp - Prevents state leaking between testsCompleter for async control - Allows testing intermediate statesFuture.delayed(Duration.zero) - Allows async operations to processisWaiting and isFailed - Cover loading and error statesAsk the user:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer