mcpField
T
testing

testing

github.com/verygoodopensourceTesting & quality17595 installsUpdated 3d ago
Repository

About testing

A testing skill for Dart and Flutter developers that applies VGV conventions to unit and widget tests. It covers readable structure, isolation, mocktail, widget helpers, golden tests, and guidance for running tests.

Key features

  1. 01Descriptive hierarchical test naming
  2. 02Isolated setup and private mocks
  3. 03mocktail-based mocking conventions
  4. 04Widget tests with pumpApp
  5. 05Golden test tagging and visual checks
  6. 06Test MCP tool execution guidance

Use cases

  1. 01Organizing Dart unit tests
  2. 02Writing behavior-focused widget tests
  3. 03Reducing shared state between tests
  4. 04Running tests in Flutter monorepos

MCP support

The skill includes guidance for running tests with a test MCP tool, including directory and timeout options. It names mcp__very-good-cli__test as a Claude Code option and describes command-line fallbacks when that server is unavailable.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/verygoodopensource/vgv-ai-flutter-plugin --skill testing

After installing, type/testingin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

agents/
openai.yaml
references/
animation-testing.md
configuration.md
coverage.md
golden-tests.md
matchers.md
mocktail.md
test-patterns.md
widget-tests.md

Details

From the project README

Dart & Flutter Testing

VGV testing conventions for Dart and Flutter. The framework APIs are assumed; what follows is the house style layered on top of them.

Core Standards

Apply these standards to all test work:

  • Descriptive test names — verbose, readable names that describe the behavior; never 'works' or 'renders'
  • Hierarchical group/test structure that reads as natural sentences — top-level group for the class, nested group for the method, test for the behavior (e.g., UserRepository → getUser → returns User when API succeeds)
  • String interpolation for type references — use 'returns $User' not 'returns User' so renames propagate automatically
  • Private mocks per file — declare class _MockX extends Mock implements X {} with underscore prefix to prevent cross-file coupling
  • Contained test setup within groups — all setUp/tearDown calls live inside a group, never at the top level of main()
  • Initialize mutable objects in setUp() with late — declare late MyDep dep; then assign in setUp so each test gets a fresh instance
  • No shared mutable state between tests — never use static members, global variables, or top-level final instances that persist across tests
  • Use package:mocktail — never package:mockito
  • Constant test tags — use an abstract class TestTag with static const fields; never pass raw string literals as tags
  • Test behavior, not properties — widget tests focus on functional outcomes; static visual properties validated via golden tests
  • Use pumpApp test helper — wrap widgets via shared helper in test/helpers/pump_app.dart; never inline pumpWidget(MaterialApp(...))
  • Tag all golden tests — annotate with TestTag.golden so goldens can run/update independently
  • Pass directory to the test MCP tool when the project is not at the workspace root — monorepos with the Flutter project in a subdirectory (e.g. mobile/) require directory: 'mobile'; omit it only when pubspec.yaml is at the workspace root
  • Pass timeout_seconds to the test MCP tool — Flutter tests can hang indefinitely when pumpAndSettle() is called without a timeout; set a cap (e.g. timeout_seconds: 120) so the run is killed instead of stalling
  • Cross-harness fallback for the test MCP tool — on Claude Code use mcp__very-good-cli__test; on a host without this plugin's Bash hooks and without that MCP server connected, run very_good test (or flutter test / dart test) directly with the same coverage and timeout options — never block on a missing MCP server

File Organization

ConventionRule
File suffixEvery test file ends with _test.dart
DirectoryAll tests live under test/
Mirror structuretest/ mirrors lib/ exactly — lib/src/models/user.dart → test/src/models/user_test.dart
HelpersShared test utilities go in test/helpers/ (e.g., pump_app.dart, fakes.dart)

Group and Test Hierarchy

Structure groups so that concatenated descriptions read as natural sentences. Use the PascalCase type itself — not a string — in the top-level group.

class _MockApiClient extends Mock implements ApiClient {}

void main() {
  group(UserRepository, () {
    late ApiClient apiClient;
    late UserRepository subject;

    setUp(() {
      apiClient = _MockApiClient();
      subject = UserRepository(apiClient: apiClient);
    });

    group('getUser', () {
      test('returns $User when API call succeeds', () async {
        when(() => apiClient.fetchUser(any()))
            .thenAnswer((_) async => User(id: '1', name: 'Dash'));

        final result = await subject.getUser('1');

        expect(result, equals(User(id: '1', name: 'Dash')));
        verify(() => apiClient.fetchUser('1')).called(1);
      });

      test('throws $UserNotFoundException when API returns 404', () {
        when(() => apiClient.fetchUser(any()))
            .thenThrow(ApiException(statusCode: 404));

        expect(
          () => subject.getUser('1'),
          throwsA(isA<UserNotFoundException>()),
        );
      });
    });
  });
}
Naming Conventions
PatternExample
Returns a value'returns $User when API call succeeds'
Throws an exception'throws $UserNotFoundException when user is not found'
Calls a dependency'calls apiClient.deleteUser with correct id'
Emits states'emits [loading, success] when data is fetched'
Conditional behavior'returns cached value when cache is not expired'
Edge case'returns empty list when repository has no items'

Test Isolation

Each test must pass when run individually, in any order, and in parallel. Use --test-randomize-ordering-seed random to expose hidden dependencies.

Anti-PatternProblemCorrect Approach
setUp at the top level of main()Breaks when test runner merges files for optimizationMove setUp inside a group
final dep = _MockDep(); (top-level)Same instance shared across all tests; state leaksUse late + setUp inside a group
class MockDep extends Mock (public)Other test files can import and depend on itUse class _MockDep extends Mock (private)
Static/global mutable variablesState persists across testsReset in setUp or avoid entirely
Tests that must run in a specific orderFragile, fails with random orderingMake each test fully self-contained

Testing Private Logic

Never test private methods directly. Exercise private logic through the public method that uses it:

// If _normalizeEmail is private, test it through the public createUser method:
test('normalizes email to lowercase before saving', () async {
  when(() => repository.save(any())).thenAnswer((_) async {});

  await subject.createUser(email: '[email protected]');

  final captured = verify(() => repository.save(captureAny())).captured;
  expect(captured.first.email, equals('[email protected]'));
});

Widget Testing

RuleDetails
Use testWidgetsEvery widget test uses testWidgets instead of test
Prefer find.byTypeDefault finder; use find.text for user-visible content, find.byKey only when type/text is ambiguous
Group by behavior categoryUse renders, navigates, calls [MethodName], updates as nested group names
Focus on behaviorAssert what the widget does (shows text, calls callback, navigates); use golden tests for visual appearance
Mock Blocs and CubitsUse MockBloc/MockCubit from package:bloc_test; never provide real Blocs in widget tests
Prefer pump() over pumpAndSettle()pumpAndSettle hangs on infinite animations such as CircularProgressIndicator
pumpApp Helper

Every widget test wraps the widget under test through one shared helper, so no test inlines its own MaterialApp:

// test/helpers/pump_app.dart
extension PumpApp on WidgetTester {
  Future<void> pumpApp(Widget widget) {
    return pumpWidget(
      MaterialApp(
        home: widget,
      ),
    );
  }
}

Export it from test/helpers/helpers.dart so every test imports it with one line:

import '../helpers/helpers.dart';

void main() {
  group(MyWidget, () {
    testWidgets('renders greeting text', (tester) async {
      await tester.pumpApp(const MyWidget());

      expect(find.text('Hello'), findsOneWidget);
    });
  });
}
Widget Testing Anti-Patterns
Anti-PatternProblemCorrect Approach
Inline MaterialApp in each testDuplicated boilerplate; inconsistent setupUse pumpApp helper
find.byKey as default finderCouples tests to implementation keysPrefer find.byType or find.text
Testing padding, colors, or font sizesFragile; breaks on every design tweak; asserts appearance, not behaviorUse a golden test for visual validation
Missing pump() after interactionWidget tree does not rebuild; assertion sees stale stateAlways pump() after tap, enterText, etc.
Real Blocs in widget testsTests become integration tests; slow, brittle, hard to isolateUse MockBloc/MockCubit from bloc_test

Padding, background color, and font size are the recurring temptation. Asserting them in a widget test couples the test to the design system and breaks on every restyle — move them to a golden test tagged TestTag.golden.

Additional Resources

Comments

Markdown supported