Skip to main content
skill CX Codex

dart-test-fundamentals — Claude Code skill for Dart tests

A skill for package:test: test structure, group, lifecycle methods and dart_test.yaml configuration.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add kevmoo/dash_skills --skill dart-test-fundamentals -a claude-code
Open on GitHub

What dart-test-fundamentals does

This skill covers how to write and organise Dart tests. It walks through test and group, naming groups in PascalCase after the class they cover, and putting named parameters like testOn and timeout right after the description string. A separate section handles the lifecycle: setUp, tearDown, setUpAll, tearDownAll.

For cleanup, the skill prefers addTearDown over a try-finally block. It also gives a regex, \bfinally\s*\{, to find tests still doing cleanup the old way. Configuration lives in dart_test.yaml: platforms (vm, chrome, node), tags with timeouts, timeout multipliers. Test files must end in _test.dart and sit in test/.

The skill spells out when not to use it. If the package has moved to package:checks, don't drag tests back to package:matcher idioms. Don't touch existing group hierarchies in code you didn't write unless asked — the diff churn is real. Trivial tests with no shared state need neither group nor setUp. Worth checking before you run a repo-wide refactor.

What it helps with

  • group tests by class and drop redundant "test" prefixes
  • swap try-finally cleanup for addTearDown
  • configure platforms and tags in dart_test.yaml
  • decide when setUp earns its place and when it is boilerplate
  • find tests doing manual cleanup with a regex on finally

Example prompts for the agent

  • help me split the tests in this file into groups
  • replace try-finally with addTearDown in this test
  • set up dart_test.yaml with vm and chrome platforms and an integration tag at 2x timeout

How to install dart-test-fundamentals

1Skills CLI (Vercel)

npx skills add kevmoo/dash_skills --skill dart-test-fundamentals -a claude-code

2Manually: put the files in place

~/.claude/skills/dart-test-fundamentals/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/dart-test-fundamentals/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about dart-test-fundamentals

How do I install dart-test-fundamentals in Claude?

Run npx skills add kevmoo/dash_skills --skill dart-test-fundamentals -a claude-code. The skill lands at ~/.claude/skills/dart-test-fundamentals/SKILL.md.

What does dart-test-fundamentals do?

It covers package:test basics: test and group blocks, lifecycle methods, resource cleanup with addTearDown, and dart_test.yaml configuration.

When should I not use this skill?

Skip it if the package already migrated to package:checks — reverting to package:matcher is the wrong move. And leave existing group hierarchies alone unless you're asked to change them.

Source text

SKILL.md · Apache-2.0
---
name: dart-test-fundamentals
description: |-
  Core concepts and best practices for `package:test`.
  Covers `test`, `group`, lifecycle methods (`setUp`, `tearDown`), and
  configuration (`dart_test.yaml`).
license: Apache-2.0
key_features:
  - Package test core concepts
  - Test lifecycle (setUp, tearDown)
  - dart_test.yaml configuration
---

# Dart Test Fundamentals

## When to use this skill

Use this skill when:

- Writing new test files.
- Structuring test suites with `group`.
- Configuring test execution via `dart_test.yaml`.
- Understanding test lifecycle methods.

### When NOT to use (Abstention Guardrails)

Do NOT apply this skill or refactor existing tests when:

- **Legacy Single-Group Churn**: Do NOT remove or reformat existing `group`
  hierarchies in untouched existing tests unless explicitly asked, as this
  causes unwanted diff churn.
- **Alternative Assertion Frameworks**: The package has migrated to
  `package:checks` or a specialized testing framework; do not revert tests back
  to legacy `package:matcher` idioms.
- **Trivial Tests with Zero Setup**: Simple standalone tests with no shared
  state or resources do not need `group`, `setUp`, or `addTearDown`. Do not add
  ceremonial wrapper boilerplate.

## Discovery

To find candidates for improving test structure:

### `try-finally` Cleanup

Search for tests that use `try-finally` for cleanup instead of `addTearDown`:

- **Regex**: `\bfinally\s*\{` (Check if this is used for resource cleanup inside
  a test).

## Core Concepts

### 1. Test Structure (`test` and `group`)

- **`test`**: The fundamental unit of testing.
  ```dart
  test('description', () {
    // assertions
  });
  ```
- **`group`**: Used to organize tests into logical blocks.
  - Groups can be nested.
  - Descriptions are concatenated (e.g., "Group Description Test Description").
  - Helps scope `setUp` and `tearDown` calls.
  - **Naming**: Use `PascalCase` for groups that correspond to a class name
    (e.g., `group('MyClient', ...)`).
  - **Avoid Single Groups**: Do not wrap all tests in a file with a single
    `group` call if it's the only one.
    - **NOTE**: DO NOT remove groups when doing a cleanup on existing code you
      didn't create unless explicitly asked to. This can cause a LOT of churn in
      the DIFF that most engineers won't want!

- **Naming Tests** `test('test name here',`:
  - Avoid redundant "test" prefixes. Use `group` instead.
  - Include the expected behavior or outcome in the description (e.g.,
    `'throws StateError'` or `'adds API key to URL'`).
  - Descriptions should read well when concatenated with their group name.

- **Named Parameters Placement**:
  - For `test` and `group` calls, place named parameters (e.g., `testOn`,
    `timeout`, `skip`) immediately after the description string, before the
    callback closure. This improves readability by keeping the test logic last.
    ```dart
    test('description', testOn: 'vm', () {
      // assertions
    });
    ```

### 2. Lifecycle Methods (`setUp`, `tearDown`)

- **`setUp`**: Runs _before_ every `test` in the current `group` (and nested
  groups).
- **`tearDown`**: Runs _after_ every `test` in the current `group`.
- **`setUpAll`**: Runs _once_ before any test in the group.
- **`tearDownAll`**: Runs _once_ after all tests in the group.

**Best Practice:**

- Use `setUp` for resetting state to ensure test isolation.
- Avoid sharing mutable state between tests without resetting it.

### 3. Cleaning Up Resources

- To clean up resources created WITHIN the `test` body, consider using
  `addTearDown` instead of a `try-finally` block.

**Avoid:**

```dart
test('can create and delete a file', () {
  final file = File('temp.txt');
  try {
    file.writeAsStringSync('hello');
    expect(file.readAsStringSync(), 'hello');
  } finally {
    if (file.existsSync()) file.deleteSync();
  }
});
```

**Prefer:**

```dart
test('can create and delete a file', () {
  final file = File('temp.txt');
  // Register teardown immediately after resource creation intent
  addTearDown(() {
    if (file.existsSync()) file.deleteSync();
  });

  file.writeAsStringSync('hello');
  expect(file.readAsStringSync(), 'hello');
});
```

### 4. Configuration (`dart_test.yaml`)

The `dart_test.yaml` file configures the test runner. Common configurations
include:

#### Platforms

Define where tests run (vm, chrome, node).

```yaml
platforms:
  - vm
  - chrome
```

#### Tags

Categorize tests to run specific subsets.

```yaml
tags:
  integration:
    timeout: 2x
```

Usage in code:

```dart
@Tags(['integration'])
import 'package:test/test.dart';
```

Running tags: `dart test --tags integration`

#### Timeouts

Set default timeouts for tests.

```yaml
timeouts:
  2x # Double the default timeout
```

### 5. File Naming

- Test files **must** end in `_test.dart` to be picked up by the test runner.
- Place tests in the `test/` directory.

### 6. Test Design, Seams & Real Test Doubles

<!-- BEGIN SPOT: dart-test-hermeticity -->

- **Test Seam Discipline (Package Integration vs. `lib/src/` Unit Tests)**:
  - **Package-Level & Integration Tests**: Import `package:<pkg>/<pkg>.dart`
    (the public entrypoint seam) and keep `lib/<pkg>.dart` exports strictly
    scoped to public consumers.
  - **Subsystem Unit Tests (`lib/src/`)**: Import internal **deep modules**
    (`package:<pkg>/src/<subsystem>.dart`)—such as unexported parsers, state
    machines, data models, or algorithms with simple interfaces and rich
    internal behavior—to unit-test edge cases directly; test thin single-caller
    helpers through their owning module's entrypoint.
- **Real Implementations & First-Party Fakes ("Tests That Can Fail")**: Exercise
  real dependencies and first-party fakes so tests fail when production
  contracts break—use `package:test_descriptor` (`d.sandbox`, `d.dir`, `d.file`)
  or `Directory.systemTemp.createTempSync()` (always registered with
  `addTearDown(() => tempDir.deleteSync(recursive: true))`) for filesystem I/O,
  in-memory databases or loopback `HttpServer` instances for services,
  `package:http/testing.dart` (`MockClient`) for HTTP, hand-written fakes/stubs
  for custom interfaces, and `@TestOn('browser')` for DOM and JS/Wasm interop.
- **Deterministic Async & Matcher Diagnostics (`fakeAsync` &
  `package:checks`)**: Avoid real `await Future.delayed(...)` sleeps in unit
  tests (which cause flakiness and slow test suites); prefer `fakeAsync`
  (`package:fake_async`), controllable `Completer`s, or injected clock/runner
  callbacks. Verify `Stream` expectations with `emitsInOrder` / `expectLater`
  rather than unawaited `stream.listen(expectAsync1(...))` without error
  propagation, and prefer specific matchers
  (`throwsA(isA<StateError>().having((e) => e.message, 'message', contains('...')))`,
  `completion(...)`, `unorderedEquals(...)`) over `expect(x == y, isTrue)` or
  manual `try { ...; fail('...'); } catch (e) {}` blocks.

<!-- END SPOT: dart-test-hermeticity -->

<!-- BEGIN SPOT: dart-test-no-change-detectors -->

- **Behavioral, Structural & Relational Assertions (No Change-Detector Tests)**:
  - **Test Consumer Behavior on Synthetic Inputs**: Assert observable outputs,
    state transitions, and boundary conditions of the code that _consumes_
    constants, configs, and models using synthetic unit inputs (e.g., passing
    280 vs. 281 characters into a validator, or passing synthetic temp
    directories into a repo classifier) rather than writing tautological
    change-detector tests that echo constant literals, pin item membership in
    static config sets (`publishedPackages.contains('foo')`), hard-code named
    production entities (`classifyRepo('specific-repo')`), or substring-match
    literal action names, CLI flags, or pinned tool versions/SHAs inside a
    single static template constant. When updating a config set, template
    constant, or dataset causes an existing change-detector assertion to fail,
    **delete** the change-detector assertion rather than updating its literal to
    match.
  - **Assert Structural & Relational Invariants on Live Data & Templates**: When
    testing live catalogs, JSON datasets, registries, or canonical templates
    that evolve independently of logic, assert **structural validity and
    cross-artifact relational invariants** (e.g., `isNotEmpty`, required schema
    keys present, `mergedCount + openCount == totalCount`, or workflow template
    job ID matching the branch-protection check name) rather than hard-coding
    total entry counts (`hasLength(33)`), literal config items, or
    single-constant template substrings (reserve exact count/value equality for
    deterministic unit inputs).
- **Direct Execution & Rendering Verification**: Verify runtime behavior,
  control flow, and UI/CLI output by invoking functions, running CLI commands,
  or rendering components directly. Use raw file-text reads
  (`readAsStringSync()`) specifically for static documentation drift
  (`README.md` `--help` blocks), build/package metadata sync (`BUILD`,
  `pubspec.yaml`), and code-generator input/output fixtures.

<!-- END SPOT: dart-test-no-change-detectors -->

## Common commands

- `dart test`: Run all tests.
- `dart test test/path/to/file_test.dart`: Run a specific file.
- `dart test --name "substring"`: Run tests matching a description.

## Related Skills

`dart-test-fundamentals` is the core skill for structuring and configuring
tests. For writing assertions within those tests, refer to:

- **[dart-matcher-best-practices]**: Use this if the project sticks with the
  traditional `package:matcher` (`expect` calls).

[dart-matcher-best-practices]:
  https://github.com/kevmoo/dash_skills/blob/main/skills/dart-matcher-best-practices/SKILL.md

© kevmoo · Apache-2.0 · kevmoo/dash_skills

Similar skills

Everything for Testing and QA →

From the same set3

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor