--- 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 - **Test Seams (`lib/.dart` vs. `lib/src/`)**: - Import `package:/.dart` for package-level and integration tests, keeping `lib/.dart` exports strictly scoped to public consumers. - Import `package:/src/.dart` directly when unit-testing an internal **deep module** (e.g., an unexported parser, state machine, or data structure with a simple interface and rich behavior), while testing thin single-caller helpers through their owning module's entrypoint. - **Real Implementations & First-Party Fakes (`Real -> Fake -> Stub`)**: - Exercise real dependencies and first-party fakes so tests fail when production contracts change: use `package:test_descriptor` (`d.sandbox`, `d.dir`, `d.file`) or `Directory.systemTemp.createTempSync()` for filesystem I/O, in-memory stores or loopback `HttpServer` instances for services, `package:http/testing.dart` (`MockClient`) for HTTP, and hand-written fakes/stubs for custom interfaces. - Run browser, DOM, and JS/Wasm interop tests on a real browser runtime (`@TestOn('browser')`). - **Behavioral & Execution-Driven Assertions**: - **Boundary & Consumer Behavior**: Test the observable outputs and boundary conditions of functions that consume constants and models (e.g., `validate('a' * 280)` vs. `validate('a' * 281)`) against concrete expected values. - **Direct Execution & Rendering**: Verify runtime behavior, control flow, and UI/CLI output by invoking functions or rendering components directly. Use raw file-text reads (`readAsStringSync()`) specifically for static `README.md` `--help` drift checks, `BUILD` / `pubspec.yaml` metadata sync, and codegen fixtures. ## 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