fl-testing
Writes unit and widget tests for blocs, repositories, and screens using bloc_test + mocktail
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Writes unit and widget tests for blocs, repositories, and screens using bloc_test + mocktail
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Behavioral guidelines for Flutter base tasks: clarify ambiguity, keep changes simple and surgical, and define verifiable success criteria before coding.
Scaffolds a new feature module under apps/main/lib/presentation/modules using the bundled module generator
Reviews UI-layer changes — screens, blocs, widgets, routes — against the template's StateBase + CoreBlocBase + fl_theme conventions
Awareness index of every reusable widget in fl_ui, fl_theme, fl_media, and core's common_widget — name, one-line purpose, when to reach for it instead of writing a new one
Builds the data layer with Freezed DTOs, Retrofit clients, the storage-seam local data manager, and repositories wired through injectable
Teaches and applies Flutter/Dart dependency injection with Injectable + GetIt, grounded in this repo's Clean Architecture and code generation conventions. Use when changing DI wiring, adding BLoCs/use cases/repositories/modules, using @Named/@preResolve/@factoryParam/env registrations, reviewing DI best practices, or setting up DI tests.
| name | fl-testing |
| description | Writes unit and widget tests for blocs, repositories, and screens using bloc_test + mocktail |
| license | MIT |
| metadata | {"audience":"flutter-developers","framework":"flutter","pattern":"testing"} |
Tests live next to the package they cover:
apps/main/test/ # app-level tests
core/test/ # core-level tests
modules/data_source/test/
plugins/<plugin>/test/
A common shape inside apps/main/test/:
test/
├── unit/
│ ├── bloc/
│ ├── domain/
│ └── data/
├── widget/
└── helpers/
fvm flutter test # current package
fvm flutter test test/unit/bloc/foo_bloc_test.dart
make coverage_main # coverage on apps/main, via coverage.sh
Use project make targets when they exist. For direct commands, check .fvm_cache; when USING_FVM=1, invoke fvm flutter test, fvm flutter analyze, or fvm dart analyze instead of local tools. Do not assume every branch has aggregate analyze/test targets.
Use Playwright MCP browser tools only when the user asks for E2E or Playwright verification of Flutter web changes. Don't add repo-level Node/Playwright infrastructure unless asked.
Build only the affected web package from the repo root, using that package's normal FVM build command. Serve the resulting build/web directory with SPA fallback before navigating, because path URL strategy routes must resolve to index.html.
For assertions, verify the behavior the user requested: URL changes, visible/semantic UI state, route state intentionally exposed by the app, and browser console errors. A missing favicon 404 can be noted separately from app failures.
Use the flutter-skill MCP only when the user asks for E2E or spec verification on a native debug build, same rule as Playwright. The project already includes the flutter_skill dep in apps/main, the debug-only binding in AppDelegate.run, and the flutter-skill MCP server registration in .mcp.json.
If no debug session is running, start one yourself when a simulator/emulator is available and the local Flutter/FVM toolchain is available:
fvm flutter run -t lib/main.dart --flavor dev -d <id>
Ask the user only for prerequisites you cannot perform yourself, such as installing/loading the flutter-skill MCP server or booting a missing simulator.
See AGENTS.md under "E2E testing (flutter_skill)" for the version pin and wrapper script.
Driving flow: inspect_interactive for the element tree, then tap / enter_text / screenshot / wait_for_idle. The inspector returns refs as key:<name> (preferred) or text:<label> when no Key is set; use tap_at for coordinate-driven taps.
Use bloc_test for sequence assertions and mocktail for collaborators.
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class _MockUsecase extends Mock implements FeatureUsecase {}
void main() {
late _MockUsecase usecase;
late FeatureBloc bloc;
setUp(() {
usecase = _MockUsecase();
bloc = FeatureBloc(null, usecase);
});
tearDown(() => bloc.close());
group('FeatureBloc', () {
test('initial state is FeatureInitial with empty data', () {
expect(bloc.state, isA<FeatureInitial>());
expect(bloc.state.data.detail, isNull);
});
blocTest<FeatureBloc, FeatureState>(
'GetFeatureEvent loads detail and stays in FeatureInitial',
build: () {
when(() => usecase.getById('1'))
.thenAnswer((_) async => Item(id: '1', name: 'A'));
return bloc;
},
act: (b) => b.add(GetFeatureEvent('1')),
expect: () => [
isA<FeatureInitial>().having((s) => s.detail?.id, 'detail.id', '1'),
],
);
});
}
Note: this template's blocs use abstract class state hierarchies, not freezed unions, so assert with isA<FeatureInitial>() plus having(...) rather than equality on a sealed union.
class _MockApi extends Mock implements UserApiClient {}
void main() {
late _MockApi api;
late UserRepositoryImpl repo;
setUp(() {
api = _MockApi();
repo = UserRepositoryImpl(api);
});
test('getUser delegates to api client', () async {
final user = UserModel(id: '1', name: 'A');
when(() => api.getUser('1')).thenAnswer((_) async => user);
expect(await repo.getUser('1'), user);
verify(() => api.getUser('1')).called(1);
});
}
Wrap the screen in the same providers it gets in production: a BlocProvider (with a mocked bloc) and MaterialApp.router or a plain MaterialApp with Localizations if your widget reads context.l10n.
class _MockBloc extends MockBloc<FeatureEvent, FeatureState>
implements FeatureBloc {}
void main() {
late _MockBloc bloc;
setUp(() => bloc = _MockBloc());
Widget pump(Widget child) => MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: BlocProvider<FeatureBloc>.value(value: bloc, child: child),
);
testWidgets('shows empty state when items is empty', (tester) async {
when(() => bloc.state).thenReturn(
FeatureInitial(data: const _StateData()),
);
await tester.pumpWidget(pump(const FeatureScreen()));
expect(find.byType(EmptyData), findsOneWidget);
});
}
For interactions, drive a real bloc through the actions extension instead of mocking — it catches more bugs.
Fallback values for any value-typed argument matchers (registerFallbackValue(...)).verifyNever/verifyInOrder over loose verify.Mock instances between tests — recreate in setUp.lib/.../foo_bloc.dart ↔ test/unit/bloc/foo_bloc_test.dart).tearDown.mocktail consistently across the file.fvm flutter test passes locally before commit.state == FeatureLoaded(...) — equality is reference-based on these abstract state classes; use isA<>().having(...).registerFallbackValue for typed arguments and getting cryptic mocktail errors.MaterialApp ancestor; localizations and themes blow up.