| name | golden_test |
| description | Create Golden tests for an existing feature - Deterministic, convention-compliant golden test generation for feature pages |
Create Golden Tests for a Feature
This skill guides the AI agent in creating golden tests for Flutter feature pages, adhering strictly to the established architecture and patterns.
Reference implementation: test/feature_profile/ — this is the canonical example
Inputs Required
To successfully execute this skill, the following inputs MUST be provided:
- Feature name: A feature name or its project path (e.g.,
feature_profile or lib/feature_profile)
Core Principle
Convention conformance > Visual coverage > Quantity
Every golden test must follow the exact patterns from the reference implementation. The agent must understand the infrastructure before generating any test.
Scope
Golden tests cover whole pages/screens only.
- One golden test file per page
- Placed in
test/feature_<name>/view/
- Individual UI components do NOT require separate golden tests
Infrastructure Overview
Package
The project uses the Alchemist package (package:alchemist/alchemist.dart).
Global Config: test/flutter_test_config.dart
- File path resolution: Strips
_light / _dark suffix from the scenario name and routes the .png into goldens/light_theme/ or goldens/dark_theme/
- Comparator:
ProcreditFileComparator with 0% tolerance
- MaterialApp wrapper: Wraps with
ProI18n, I18n, GlobalMaterialLocalizations and DesignSystem.fromBrightness(Brightness.light) theme
- Default locale:
Locale('bg')
You never specify a path to goldens/ manually. The scenario name + theme enum determine the output path automatically.
Theme Enum: test/helpers/enums/app_themes.dart
enum Themes {
light,
// dark, (currently disabled)
}
Default Devices: test/helpers/golden_helper.dart
Every golden renders across 7 devices:
| Device | Size | Pixel Ratio | Safe Area |
|---|
| iPhone SE (2nd gen) | 375×667 | 2.0 | top: 20 |
| Google Pixel 4a | 412×732 | 1.0 | none |
| iPhone 13 mini | 375×812 | 3.0 | top: 44, bottom: 34 |
| Google Pixel 5 | 393×851 | 2.75 | top: 24, bottom: 48 |
| Samsung Galaxy S20 | 412×915 | 3.0 | top: 24, bottom: 48 |
| Samsung Galaxy Tab S6 | 1280×800 | 2.0 | top: 24, bottom: 48 |
| Apple iPad Pro 12.9 | 1024×1366 | 2.0 | top: 24, bottom: 34 |
Directory Structure (MANDATORY)
test/feature_<name>/
├── factory/ # Widget factories for golden tests
│ └── <page_name>_factory.dart
├── mock/ # Mockito mock factories + generated files
│ ├── <bloc_name>_mock.dart
│ └── <bloc_name>_mock.mocks.dart # Auto-generated by build_runner
└── view/ # Golden test files + generated images
├── goldens/
│ └── light_theme/
│ └── <scenario_name>.png # Auto-generated by --update-goldens
└── <page_name>_golden_test.dart
- NEVER manually create or move
.png files
- Dark theme images go in
goldens/dark_theme/ (when enabled)
File Naming Conventions (MANDATORY)
| Artifact | Pattern | Example |
|---|
| Golden test file | <page_snake_case>_golden_test.dart | profile_golden_test.dart |
| Factory file | <page_snake_case>_factory.dart | profile_factory.dart |
| Mock file | <bloc_snake_case>_mock.dart | profile_mock.dart |
| Generated mock | <bloc_snake_case>_mock.mocks.dart | profile_mock.mocks.dart |
| Golden image | <scenario_name>.png (auto-resolved) | profile_success.png |
Scenario Naming (MANDATORY)
The scenario name passed to Scenario(name: '...') becomes the golden filename.
Format:
<page_prefix>_<state>[_<variant>]
Rules:
- Always snake_case
- Start with the page prefix (e.g.,
profile_, branch_network_page_)
- End with the state/variant (e.g.,
_success, _loading, _error)
- Must be unique across the test file
- The
_light / _dark suffix is appended automatically — never add it yourself
Examples:
| Scenario Name | Meaning |
|---|
profile_success | default success state |
profile_success_toggled | success with biometrics/notifications enabled |
profile_loading | loading state |
profile_error | error state |
Step-by-Step Execution Plan
1. Analysis Phase (Before Generating)
Before generating golden tests for any feature, the agent MUST:
2. Create BLoC Mock Factories
In test/feature_<name>/mock/<bloc>_mock.dart:
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:procredit/<feature>/blocs/<bloc>.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart';
import '<bloc>_mock.mocks.dart';
@GenerateMocks([<Bloc>BlocStates, <Bloc>BlocEvents, <Bloc>BlocType])
<Bloc>BlocType <bloc>MockFactory({
Result<MyDomainModel>? dataResult,
bool? someToggle,
// ... all state parameters
}) {
final blocMock = Mock<Bloc>BlocType();
final eventsMock = Mock<Bloc>BlocEvents();
final statesMock = Mock<Bloc>BlocStates();
when(blocMock.events).thenReturn(eventsMock);
when(blocMock.states).thenReturn(statesMock);
// Wire each state stream
when(statesMock.dataResult).thenAnswer(
(_) => dataResult != null ? Stream.value(dataResult) : const Stream.empty(),
);
when(statesMock.someToggle).thenAnswer(
(_) => someToggle != null ? Stream.value(someToggle) : const Stream.empty(),
);
// ... wire all remaining states ...
return blocMock;
}
Critical Mock Rules:
@GenerateMocks goes in the mock file, never in the test or factory file
- Every state exposed by the BLoC's States class must be wired (prevents
MissingStubError)
- Null/absent parameters should emit
const Stream.empty()
- Run
dart run build_runner build after creating/modifying mock files
Stream wiring conventions:
| Stream type | Pattern |
|---|
| Simple fire-once (errors, loading) | Stream.value(x) or const Stream.empty() |
| States needing replay (dialogs, toggles) | .publishReplay(maxSize: 1)..connect() |
| States needing sharing | .share() or .shareReplay(maxSize: 1) |
3. Create Widget Factory
In test/feature_<name>/factory/<page>_factory.dart:
import 'package:flutter/material.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:procredit/<feature>/blocs/<bloc>.dart';
import 'package:procredit/<feature>/views/<page>.dart';
import 'package:provider/provider.dart';
import 'package:rx_bloc/rx_bloc.dart';
import '../mock/<bloc>_mock.dart';
Widget <page>Factory({
Result<MyDomainModel>? dataResult,
bool? someToggle,
// ... all state parameters
}) => Scaffold(
body: MultiProvider(
providers: [
RxBlocProvider<MyFeatureBlocType>.value(
value: myFeatureMockFactory(
dataResult: dataResult,
someToggle: someToggle,
),
),
// ... all required providers
],
child: const <Page>(),
),
);
Critical Factory Rules:
- Use expression body (
=> Scaffold(...)) — no block body, no local variables
- Accept optional/named parameters for each UI state dimension
- Parameters map 1:1 to BLoC state streams
- Wrap the page in
Scaffold body
- Provide all required BLoC providers via
MultiProvider + RxBlocProvider<T>.value
Factory MUST NEVER:
- Import
package:mockito/mockito.dart
- Import
../../mocks/stubs.dart
- Contain
when() / thenAnswer() / thenReturn() calls
- Instantiate real services
- Contain any stubbing logic
4. Create Golden Test File
In test/feature_<name>/view/<page>_golden_test.dart:
import 'package:procredit/base/models/...'; // Domain model imports as needed
import 'package:rx_bloc/rx_bloc.dart';
import '../../helpers/golden_helper.dart';
import '../../helpers/models/scenario.dart';
import '../../mocks/stubs.dart';
import '../factory/<page>_factory.dart';
void main() {
runGoldenTests([
generateDeviceBuilder(
widget: <page>Factory(
dataResult: Result.success(Stubs.mockModel),
),
scenario: Scenario(name: '<page>_success'),
),
generateDeviceBuilder(
widget: <page>Factory(
dataResult: Result.loading(),
),
scenario: Scenario(name: '<page>_loading'),
),
generateDeviceBuilder(
widget: <page>Factory(
dataResult: Result.error(Stubs.error),
),
scenario: Scenario(name: '<page>_error'),
),
]);
}
Key API:
runGoldenTests(List<ScenarioBuilder>) — iterates over all Themes.values, calls goldenTest for each scenario+theme
generateDeviceBuilder(widget:, scenario:) — renders the widget across all 7 default devices
- Optional parameters:
customPumpBeforeTest, customDeviceHeight, locale
setUp() usage:
If the page requires global setup (e.g., isTest = true, VisibilityDetectorController), add a setUp() block before runGoldenTests:
void main() {
setUp(() {
isTest = true;
});
runGoldenTests([...]);
}
Test Data Rules
ALL test data MUST come from test/mocks/stubs.dart
This includes:
- Model instances (e.g.,
Stubs.profile, Stubs.error)
- Strings, numbers, IDs
- Lists and collections
Also use:
ProfileModel.withDefaults() for default profile data
Result.success(data), Result.loading(), Result.error(Stubs.error) for state wrappers
LoadingWithTag(loading: true) for loading states
Forbidden:
- Inline model construction in test or factory files
- Hardcoded literals in test or factory files
- If new data is needed, add it to
stubs.dart first
State Coverage Matrix
For every page, the agent MUST enumerate and cover:
| Dimension | Values |
|---|
| Data state | success, loading, error, empty (if applicable) |
| Interaction state | expanded/collapsed, toggled on/off, selected/unselected |
| Edge cases | missing optional data, overlay/modal visible |
| Theme | light (+ dark when enabled in Themes enum) |
| Locale | bg (default); add others only if layout visually differs |
Create one generateDeviceBuilder call per unique combination that produces a visually distinct render.
Do NOT create scenarios for combinations that render identically.
Forbidden Actions
- NEVER manually create, rename, or move
.png golden files
- NEVER use
@GenerateMocks inside a test file or factory file
- NEVER put
when() / thenAnswer() / thenReturn() calls inside a factory file
- NEVER import
package:mockito/mockito.dart in a factory file
- NEVER import
stubs.dart in a factory file
- NEVER instantiate real services inside a factory file
- NEVER use block body (
{ return ... }) in a factory — use expression body (=>)
- NEVER define test data inline in test files
- NEVER add
_light or _dark suffix to scenario names
- NEVER skip wiring a state stream in the mock factory
Reference: Key Import Paths
In golden test files:
import '../../helpers/golden_helper.dart';
import '../../helpers/models/scenario.dart';
import '../../mocks/stubs.dart';
import '../factory/<page>_factory.dart';
import 'package:rx_bloc/rx_bloc.dart'; // Result, LoadingWithTag
In factory files (NO mockito, NO stubs):
import 'package:flutter/material.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart'; // RxBlocProvider
import 'package:provider/provider.dart'; // MultiProvider, Provider
import 'package:rx_bloc/rx_bloc.dart'; // Result, LoadingWithTag
import '../mock/<bloc>_mock.dart';
In mock files:
import 'package:mockito/annotations.dart'; // @GenerateMocks
import 'package:mockito/mockito.dart'; // when, thenReturn, thenAnswer
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart'; // share, shareReplay, publishReplay
import '../../mocks/stubs.dart'; // test data (ONLY in mock files)
Finalize
-
Run build_runner to generate mock files:
dart run build_runner build --delete-conflicting-outputs
-
Generate golden images:
flutter test --update-goldens test/feature_<name>/view/<page>_golden_test.dart
-
Verify all scenarios pass:
flutter test test/feature_<name>/view/<page>_golden_test.dart
Ambiguous Case Rule
If the agent cannot clearly determine:
- Which page states are visually distinct, OR
- What BLoC states drive the UI, OR
- What provider dependencies the page requires
Then the agent MUST read the page source code and DI file before proceeding. Never guess.
By following these architecture and testing guidelines strictly, you will produce seamless, clean, scalable golden tests fully integrated into the pipeline.