dart-matcher-best-practices · git:20260912.2fb1238 · 2026-09-12 · sha256 1d6f7e0549ed067f
dart-matcher-best-practices git:20260912.2fb1238A
Immutable. This exact content is served forever at /api/v1/blob/1d6f7e0549ed067f.
---
name: dart-matcher-best-practices
description: |-
Best practices for using `expect` and `package:matcher`.
Focuses on readable assertions, proper matcher selection, and avoiding
common pitfalls.
license: Apache-2.0
key_features:
- Package matcher assertions
- Readable test expectations
- Matcher selection & refactoring
---
# Dart Matcher Best Practices
## When to use this skill
Use this skill when:
- Writing assertions using `expect` and `package:matcher`.
- Migrating legacy manual checks to cleaner matchers.
- Debugging confusing test failures.
### When NOT to use (Abstention Guardrails)
Do NOT apply this skill or refactor assertions when:
- **Non-Collection Properties**: The property happens to be named `.length` but belongs to a non-collection class (e.g. geometric scalar magnitude on a `Vector`, duration on a buffer, or line segment distance). `hasLength(n)` expects an `int` on an `Iterable`, `Map`, or `String`.
- **Custom Domain Booleans**: The boolean check tests an arbitrary domain property (e.g. `expect(switch.isEnabled, isTrue)`). Do not invent non-existent matchers.
- **Alternative Assertion Frameworks**: The package has adopted `package:checks` (`check(x)...`). Do not revert `checks` back to `package:matcher`.
- **Already Idiomatic Assertions**: Tests already use declarative matchers (`hasLength`, `isEmpty`, `isNotEmpty`, `contains`, `containsPair`, `isA`, `throwsA`). Abstain and make zero modifications.
## Discovery
To find candidates for improving matcher usage, search for suboptimal patterns:
### Suboptimal Length Checks
Search for length checks that should use `hasLength`:
- **Regex**: `expect\([^,]+.length,\s*`
### Suboptimal Boolean Checks
Search for checks on boolean properties that have specific matchers:
- **Regex**: `expect\([^,]+.isEmpty,\s*(true|equals\(true\))`
- **Regex**: `expect\([^,]+.isNotEmpty,\s*(true|equals\(true\))`
- **Regex**: `expect\([^,]+.contains\(.*\),\s*(true|equals\(true\))`
### Suboptimal Map Lookups
Search for manual map lookups instead of `containsPair`:
- **Regex**: `expect\([^,]+\[.*\],\s*`
## Core Matchers
### 1. Collections (`hasLength`, `contains`, `isEmpty`, `unorderedEquals`, `containsPair`)
- **`hasLength(n)`**:
- Prefer `expect(list, hasLength(n))` over `expect(list.length, n)`.
- Gives better error messages on failure (shows actual list content).
- **`isEmpty` / `isNotEmpty`**:
- Prefer `expect(list, isEmpty)` over `expect(list.isEmpty, true)`.
- Prefer `expect(list, isNotEmpty)` over `expect(list.isNotEmpty, true)`.
- **`contains(item)`**:
- Verify existence without manual iteration.
- Prefer over `expect(list.contains(item), true)`.
- **`unorderedEquals(items)`**:
- Verify contents regardless of order.
- Prefer over `expect(list, containsAll(items))`.
- **`containsPair(key, value)`**:
- Verify a map contains a specific key-value pair.
- Prefer over checking `expect(map[key], value)` or
`expect(map.containsKey(key), true)`.
### 2. Type Checks (`isA<T>` and `TypeMatcher<T>`)
- **`isA<T>()`**:
- Prefer for inline assertions: `expect(obj, isA<Type>())`.
- More concise and readable than `TypeMatcher<Type>()`.
- Allows chaining constraints using `.having()`.
- **`TypeMatcher<T>`**:
- Prefer when defining top-level reusable matchers.
- **Use `const`**: `const isMyType = TypeMatcher<MyType>();`
- Chaining `.having()` works here too, but the resulting matcher is not `const`.
### 3. Object Properties (`having`)
Use `.having()` on `isA<T>()` or other TypeMatchers to check properties.
- **Descriptive Names**: Use meaningful parameter names in the closure (e.g.,
`(e) => e.message`) instead of generic ones like `p0` to improve readability.
```dart
expect(person, isA<Person>()
.having((p) => p.name, 'name', 'Alice')
.having((p) => p.age, 'age', greaterThan(18)));
```
This provides detailed failure messages indicating exactly which property
failed.
### 4. Async Assertions
- **`completion(matcher)`**:
- Wait for a future to complete and check its value.
- **Prefer `await expectLater(...)`** to ensure the future completes before
the test continues.
- `await expectLater(future, completion(equals(42)))`.
- **`throwsA(matcher)`**:
- Check that a future or function throws an exception.
- `await expectLater(future, throwsA(isA<StateError>()))`.
- `expect(() => function(), throwsA(isA<ArgumentError>()))` (synchronous
function throwing is fine with `expect`).
### 5. Exception Testing (Avoid `try/catch` with `fail()`)
- **Prefer `throwsA` over imperative `try/catch`**:
- Legacy tests often use `try { fn(); fail('should throw'); } catch (e) { expect(e, isA<T>()); }`.
- Replace with declarative matchers:
```dart
// Synchronous:
expect(() => parseData('invalid'), throwsA(isA<FormatException>()));
// Asynchronous:
await expectLater(client.fetch('bad-url'), throwsA(isA<HttpException>()));
```
- Common built-in exception matchers include `throwsArgumentError`,
`throwsStateError`, `throwsRangeError`, `throwsFormatException`,
`throwsNoSuchMethodError`, and `throwsUnsupportedError`.
### 6. Using `expectLater`
Use `await expectLater(...)` when testing async behavior to ensure proper
sequencing.
```dart
// GOOD: Waits for future to complete before checking side effects
await expectLater(future, completion(equals(42)));
expect(sideEffectState, equals('done'));
// BAD: Side effect check might run before future completes
expect(future, completion(equals(42)));
expect(sideEffectState, equals('done')); // Race condition!
```
## Principles
1. **Readable Failures**: Choose matchers that produce clear error messages.
2. **Avoid Manual Logic**: Don't use `if` statements or `for` loops for
assertions; let matchers handle it.
3. **Specific Matchers**: Use the most specific matcher available (e.g.,
`containsPair` for maps instead of checking keys manually).
## Related Skills
- **[dart-test-fundamentals]**: Core
concepts for structuring tests, lifecycles, and configuration.
[dart-test-fundamentals]: https://github.com/kevmoo/dash_skills/blob/main/skills/dart-test-fundamentals/SKILL.md