Contents
- Why Migrate to package:checks
- Dependency Management
- Assertion Mapping and Syntax Guidelines
- Advanced Asynchronous Assertions
- Workflow: Executing Syntax Migrations
- Examples
Why Migrate to package:checks
package:checks is a modern assertion library maintained by the Dart team that replaces package:matcher. It offers several benefits over traditional matchers:
- Fluent API: Chain assertions naturally using Dart methods instead of nested helper functions.
- Type Safety: Benefit from compiler checks on expected properties, preventing type mismatches in tests.
- Descriptive Diagnostics: Read extremely clear failure messages showing exactly what failed and why (e.g. displaying collection differences).
Dependency Management
To add the checks library to your package:
- Add
checksas adev_dependencyusing the terminal:dart pub add dev:checks - Import the main package entrypoint in all migrating test files:
import 'package:checks/checks.dart'; - Remove explicit
package:matcherdependencies frompubspec.yaml(note thatpackage:testtransitively imports matchers, which is acceptable).
Assertion Mapping and Syntax Guidelines
Migrate legacy assertions using the following mapping guidelines:
| Scenario | Legacy Matcher (package:matcher) |
Modern Checks (package:checks) |
|---|---|---|
| Equality | expect(value, equals(expected)) |
check(value).equals(expected) |
| Identity | expect(value, same(expected)) |
check(value).identicalTo(expected) |
| Type Check | expect(value, isA<Type>()) |
check(value).isA<Type>() |
| Nullness | expect(value, isNull) |
check(value).isNull() |
| Booleans | expect(value, isTrue) |
check(value).isTrue() |
| Containment | expect(list, contains(item)) |
check(list).contains(item) |
| String Prefixes | expect(str, startsWith(prefix)) |
check(str).startsWith(prefix) |
| Property checks | expect(user.name, equals('Alice')) |
check(user).has((u) => u.name, 'name').equals('Alice') |
Chaining Multiple Checks
Use Dartโs cascade operator (..) to assert multiple properties on a single subject without writing separate expectation statements:
check(user)
..has((u) => u.name, 'name').equals('Alice')
..has((u) => u.age, 'age').isGreaterThan(20);
Advanced Asynchronous Assertions
1. Futures
To verify Future resolutions, always await the base check statement:
// Verify completion value:
await check(fetchScore()).completes((score) => score.equals(100));
// Verify throws error:
await check(failingTask()).throws<HttpException>().has((e) => e.statusCode, 'statusCode').equals(500);
2. Streams
To assert multiple sequential items emitted by a Stream, wrap the target stream in a StreamQueue from package:async:
import 'package:async/async.dart';
final queue = StreamQueue(eventStream);
await check(queue).emits((event) => event.equals('first_event'));
await check(queue).emits((event) => event.equals('second_event'));
Workflow: Executing Syntax Migrations
Follow this checklist when migrating your test suite:
- [ ] Dependency Setup: Install
dev:checksand rundart pub get. - [ ] Find target files: Locate test files containing traditional
expect()calls. - [ ] Update imports: Replace matcher imports or add
import 'package:checks/checks.dart'. - [ ] Convert simple cases: Map basic equalities and type matches from
expecttocheck. - [ ] Optimize with cascades: Group multiple assertions targeting the same object into cascades.
- [ ] Update async calls: Rewrite asynchronous Future/Stream checks to use
awaitandcompletes()closures. - [ ] Validate compiler checks: Run
dart analyzeto resolve any type mismatch warnings. - [ ] Execute tests: Run
dart testto verify that all migrated assertions execute successfully.
Examples
Migration Comparison
This example demonstrates migrating a complete, high-fidelity user profile test case from legacy matcher syntax to modern checks syntax.
// --- Legacy Matcher Assertion Style ---
import 'package:test/test.dart';
class Account {
final String email;
final List<String> tags;
Account(this.email, this.tags);
}
void badTest() {
final account = Account('alice@test.com', ['admin', 'premium']);
expect(account.email, equals('alice@test.com'));
expect(account.tags, contains('admin'));
expect(account.tags.length, equals(2));
}
// --- Modern Checks Assertion Style ---
import 'package:checks/checks.dart';
void goodTest() {
final account = Account('alice@test.com', ['admin', 'premium']);
// Chain validations cleanly using cascades and property extractors
check(account)
..has((a) => a.email, 'email').equals('alice@test.com')
..has((a) => a.tags, 'tags').contains('admin')
..has((a) => a.tags.length, 'tags length').equals(2);
}
Testing Exceptions and Future Completions
import 'package:checks/checks.dart';
import 'package:test/test.dart';
Future<String> fetchRole(bool fail) async {
await Future.delayed(const Duration(milliseconds: 10));
if (fail) throw StateError('Fetch failed');
return 'Manager';
}
void main() {
group('Asynchronous Tests', () {
test('verifies successful completion', () async {
await check(fetchRole(false)).completes(
(role) => role.equals('Manager'),
);
});
test('verifies throwing specific state errors', () async {
await check(fetchRole(true)).throws<StateError>().has(
(e) => e.message, 'message',
).equals('Fetch failed');
});
});
}