Skip to content

Testing Patterns

Testing Patterns Reference

Quick-reference for deciding which test style to use, how to structure TestContext classes, and how to name tests. For step-by-step implementation, see the Testing Strategy how-to guide.

LayerComponentTest StyleGroup By
DatasourceModel ConvertorsStandard unitInput/output cases
DatasourceFilter ConvertorsStandard unitInput/output cases
DatasourceRequest ConvertorsStandard unitInput/output cases
DatasourceCacheHandlerSpecsStandard unitHandler behavior
DatasourceStreamConvertorsStandard unitStream output cases
DomainRepository (integration)BDDBusiness scenario
ServicesOrchestrator / Cross-cuttingBDDBusiness scenario
UINotifiers (simple CRUD)Standard unitState transition
UINotifiers (complex flow)BDDBusiness scenario
UIWidgets (user scenarios)BDDUser journey
UIWidgets (rendering only)Standard unitVisual output

Use for any test file with more than 3 tests.

class [ClassUnderTest]TestContext {
[ClassUnderTest]TestContext() {
container = ProviderContainer(overrides: [ /* mock overrides */ ]);
}
late final ProviderContainer container;
// 1. Mock declarations
final mockRepo = MockRepository();
// 2. Stub methods — one per scenario setup
void stubSuccess(List<Model> data) { ... }
void stubFailure(Exception error) { ... }
// 3. Accessor — the class under test
ClassUnderTest get sut => container.read(provider.notifier);
// 4. Static factories — sample data
static List<Model> sampleData([int count = 3]) => ...;
// 5. Assertion helpers
void verifyRepoAccessed({int times = 1}) { ... }
// 6. Cleanup
void dispose() => container.dispose();
}
'Given [precondition], '
'When [action], '
'Then [observable outcome]'

Examples:

test('Given events and attendees exist, '
'When getDashboard is called, '
'Then it returns a merged dashboard from both repos', () async { ... });
test('Given the event repository fails, '
'When getDashboard is called, '
'Then it propagates the error without calling attendee repo', () async { ... });
testWidgets('Given the user sees an error, '
'When they tap Retry, '
'Then the list reloads and shows data', (tester) async { ... });

Use when a component has exhaustive state transitions.

class [Component]StateCase {
const [Component]StateCase({
required this.description,
required this.arrange,
required this.act,
required this.expectedState,
});
final String description;
final void Function(TestContext ctx) arrange;
final Future<void> Function(Component component) act;
final bool Function(State state) expectedState;
}
final cases = [
[Component]StateCase(
description: 'scenario A -> expected result',
arrange: (ctx) => ctx.stubScenarioA(),
act: (component) => component.doAction(),
expectedState: (state) => state.isExpectedResult,
),
// ... more cases
];
for (final c in cases) {
test(c.description, () async {
final ctx = TestContext();
c.arrange(ctx);
await c.act(ctx.sut);
expect(c.expectedState(ctx.state), isTrue,
reason: 'Failed: ${c.description}');
ctx.dispose();
});
}

For technical components, test names describe input and expected output:

// Convertor tests
test('converts GQL data to domain model with all fields', () { ... });
test('maps null fields to empty string defaults', () { ... });
test('builds request with pagination parameters', () { ... });
// CacheHandlerSpecs tests
test('upsert handler writes updated data to cache', () { ... });
test('delete handler removes item from cached list', () { ... });
// Simple notifier tests
test('loads attendees from repository on init', () { ... });
test('sets error state when repository throws', () { ... });
Anti-PatternWhy It’s WrongDo This Instead
Coverage percentage targetsCreates incentive to test trivial code, miss critical pathsWrite tests that prove business invariants
Group by method name for domain codeFragments related scenarios across groupsGroup by behavior scenario
Scattered setUp/tearDownDuplicated mock setup, hard to maintainUse TestContext classes
Arrange/Act/Assert comments in BDDRedundant with Given/When/Then namingLet the test name provide structure
Testing internal stateBreaks on refactor, doesn’t prove user valueAssert observable behavior and outputs