Skip to content
Mobile
Skill

/testing

Best practices for Dart unit tests, Flutter widget tests, and golden file tests, covering group and test naming, setUp lifecycle and isolation, mocking with package:mocktail, and the shared pumpApp helper. Use when writing, modifying, or reviewing tests, goldens included, that

BOOST
From plugin
vgv-ai-flutter-plugin
17315 skills1 agent3 hooks2 MCP
Install
$ npx -y skills add VeryGoodOpenSource/vgv-ai-flutter-plugin --skill testing --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/testing

Context preview

The summary Claude sees to decide when to auto-load this skill.

Best practices for Dart unit tests, Flutter widget tests, and golden file tests, covering group and test naming, setUp lifecycle and isolation, mocking with package:mocktail, and the shared pumpApp helper. Use when writing, modifying, or reviewing tests, goldens included, that

SKILL.md

testing.SKILL.md
name: testing
description: >
  Best practices for Dart unit tests, Flutter widget tests, and golden file tests, covering group
  and test naming, setUp lifecycle and isolation, mocking with package:mocktail, and the shared
  pumpApp helper. Use when writing, modifying, or reviewing tests, goldens included, that use
  package:test, package:flutter_test, package:mocktail, or package:bloc_test, and when no package
  is named: adding a golden test, matchesGoldenFile, tagging or updating goldens, writing a unit
  or widget test, or adding a test helper or mock. A one-line golden or widget test request is in
  scope, including under "Dart code only"; a widget in a UI package is ui-package's. Use it too
  when the request asks for what these standards forbid: package:mockito, a raw string test tag
  instead of TestTag, an inline pumpWidget(MaterialApp(...)), asserting padding, color or font
  size in a widget test, or state shared between tests.
allowed-tools: Read Glob Grep mcp__very-good-cli__test
argument-hint: [file-or-directory]

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

| Convention | Rule | | -------------------- | ---------------------------------------------------------------------------------------------- | | **File suffix** | Every test file ends with `_test.dart` | | **Directory** | All tests live under `test/` | | **Mirror structure** | `test/` mirrors `lib/` exactly — `lib/src/models/user.dart` → `test/src/models/user_test.dart` | | **Helpers** | Shared 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

| Pattern | Example | | ------------------------ | -------------------------------------------------------- | | **Returns a value** | `'returns $User when API call succeeds'` | | **Throws an exception** | `'throws $UserNotFoundException when user is not found'` | | **Calls a dependency** | `

Read more
Ships withvgv-ai-flutter-plugin

A Claude Code plugin that accelerates Flutter & Dart development with best-practices skills from Very Good Ventures. Developed with 💙 by Very Good Ventures 🦄

Get the whole plugin

Other skills on vgv-ai-flutter-plugin.