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.