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
grouphierarchies in untouched existing tests unless explicitly asked, as this causes unwanted diff churn. - Alternative Assertion Frameworks: The package has migrated to
package:checksor a specialized testing framework; do not revert tests back to legacypackage:matcheridioms. - Trivial Tests with Zero Setup: Simple standalone tests with no shared
state or resources do not need
group,setUp, oraddTearDown. 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. -
group: Used to organize tests into logical blocks.- Groups can be nested.
- Descriptions are concatenated (e.g., "Group Description Test Description").
- Helps scope
setUpandtearDowncalls. - Naming: Use
PascalCasefor 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
groupcall 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
groupinstead. - 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.
- Avoid redundant "test" prefixes. Use
-
Named Parameters Placement:
- For
testandgroupcalls, 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.
- For
2. Lifecycle Methods (setUp, tearDown)
setUp: Runs before everytestin the currentgroup(and nested groups).tearDown: Runs after everytestin the currentgroup.setUpAll: Runs once before any test in the group.tearDownAll: Runs once after all tests in the group.
Best Practice:
- Use
setUpfor 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
testbody, consider usingaddTearDowninstead of atry-finallyblock.
Avoid:
Prefer:
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).
Tags
Categorize tests to run specific subsets.
Usage in code:
Running tags: dart test --tags integration
Timeouts
Set default timeouts for tests.
5. File Naming
- Test files must end in
_test.dartto be picked up by the test runner. - Place tests in the
test/directory.
6. Test Design, Seams & Real Test Doubles
- Test Seams (
lib/<pkg>.dartvs.lib/src/):- Import
package:<pkg>/<pkg>.dartfor package-level and integration tests, keepinglib/<pkg>.dartexports strictly scoped to public consumers. - Import
package:<pkg>/src/<subsystem>.dartdirectly 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.
- Import
- 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) orDirectory.systemTemp.createTempSync()for filesystem I/O, in-memory stores or loopbackHttpServerinstances 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')).
- Exercise real dependencies and first-party fakes so tests fail when
production contracts change: use
- 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 staticREADME.md--helpdrift checks,BUILD/pubspec.yamlmetadata sync, and codegen fixtures.
- Boundary & Consumer Behavior: Test the observable outputs and boundary
conditions of functions that consume constants and models (e.g.,
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(expectcalls).


