Loading...
Loading...
Replace the usage of `expect` and similar functions from `package:matcher` to `package:checks` equivalents.
npx skill4agent add flutter/agent-plugins dart-migrate-to-checks-packagepackage:matcherpackage:test/test.dartpackage:checkspackage:checksdev_dependencypubspec.yamldart pub add dev:checkspackage:matcherdev_dependenciespackage:testexpectexpectLaterimport 'package:test/test.dart';import 'package:test/scaffolding.dart';
import 'package:checks/checks.dart';import 'package:test/expect.dart'; // Temporarily allows legacy expect()expectexpectLatercheckimport 'package:test/expect.dart';expectdart analyze.isA<Type>()unawaited_futuresdart testpackage:checks[!IMPORTANT] A line-for-line translation can sometimes introduce subtle bugs or false passes. Always review these key differences carefully:
equalsdeepEqualsexpect(actual, expected)expect(actual, equals(expected)).equals(expected)operator ==operator ==.equals.deepEquals(expected)// BEFORE (Matcher)
expect(myList, [1, 2, 3]);
// AFTER (Checks)
check(myList).deepEquals([1, 2, 3]);reasonbecausereasonexpectexpect(actual, expectation, reason: 'Explanation');becausecheckcheck(because: 'Explanation', actual).expectation();matchesmatchesPatternmatches(pattern)StringRegExpmatches(r'\d')'1'.matchesPattern(pattern)StringRegExp// BEFORE (Matcher)
expect(someString, matches(r'\d+'));
// AFTER (Checks)
check(someString).matchesPattern(RegExp(r'\d+'));TypeMatcher.having.hasTypeMatcher.having(feature, description, matcher)expect(actual, isA<Person>().having((p) => p.name, 'name', startsWith('A')));.has(feature, description)SubjectSubjectcheck(actual).isA<Person>().has((p) => p.name, 'name').startsWith('A');throwspackage:matcherthrowsAexpectexpectLater.throws<E>()Subject<T Function()>.throws<E>()Subject<E>Subject<E>// YES (Synchronous chaining)
check(() => triggerSyncError()).throws<ArgumentError>()
..has((e) => e.message, 'message').equals('invalid input');
// NO (Passing a callback to sync throws will cause a compiler error!)
check(() => triggerSync").throws<ArgumentError>((it) => ...); // ERROR!Subject<Future<T>>.throws<E>()Future<void>Future<void>// YES (Asynchronous callback)
await check(triggerAsyncError()).throws<ArgumentError>((it) => it
..has((e) => e.message, 'message').equals('invalid input'));.throws<E>()await check(future).throws<E>().equals(...)Future<void>package:matcherexpect(myPattern,equals(RegExp('Hello'))).equals()==RegExp==.equals().isA<RegExp>()RegExpcheck(myPattern).isA<RegExp>()
..has((r) => r.pattern, 'pattern').equals('Hello')
..has((r) => r.isMultiLine, 'isMultiLine').isTrue();bool?isTrueisFalsebool?.isTrue().isFalse()Subject<bool>Subject<bool?>bool?.isNotNull().isTrue().equals(true).equals(false)// If options.flagOutdated is a bool?
check(options.flagOutdated).equals(true);
check(options.flagOutdated).equals(false);containsKeycontainspackage:matchercontains(key)Map.contains(...)Subject<Map>.containsKey(key)// BEFORE (Matcher)
expect(myMap, contains('my_key'));
// AFTER (Checks)
check(myMap).containsKey('my_key');expect(extensionTypeConst, 3)QrEciValueintextension type const QrEciValue(int value) implements int.equals(3)Subject<QrEciValue>3intQrEciValueas intQrEciValueintcheck// YES (Type-safe and warning-free)
check<int>(QrEciValue.iso8859_1).equals(3);dynamicdynamicIterable<Object?>.deepEquals(...)ListMap// YES (Explicit cast to List)
check(myIterable).deepEquals(json['data']['items'] as List);| Legacy Matcher | Package Checks Equivalent | Notes |
|---|---|---|
| | Use |
| | Use |
| | Chaining is supported directly |
| | Verifies identity |
| | E.g. |
| | E.g. |
| | Works on String, Map, Iterable, etc. |
| | E.g. |
| | Works on String, Iterable (use |
| | Map key containment |
| | String only |
| | String only |
| | Works on String, Map, Iterable |
| | Works on String, Map, Iterable |
| | |
| | |
| | Works on non-nullable |
| | Works on non-nullable |
| | Must be awaited! |
| | Must be awaited! |
| | Must be awaited! |
| | Must be awaited! |
| | String only |
| |
package:checksthrowsArgumentErrorthrowsStateErrorthrowsUnsupportedError.throws<T>()await check(triggerError()).throws<ArgumentError>();anythingexpect(actual, anything)(_) {}await check(someFuture).completes((_) {});isPositiveisNegativeisZeroisNonPositiveisNonNegativeisNonZeroisPositiveisGreaterThan(0)isNegativeisLessThan(0)isZeroequals(0)isNonNegativeisGreaterOrEqual(0)inClosedOpenRange(min, max)inInclusiveRange(min, max)..check(actualValue)
..isGreaterOrEqual(min)
..isLessThan(max);Matcherpackage:checksextensionSubject<T>import 'package:checks/context.dart';expectcontext.expectRejectionextension CustomPersonChecks on Subject<Person> {
void isAdult() {
context.expect(
() => ['is an adult (age >= 18)'],
(actual) {
if (actual.age >= 18) return null; // Pass
return Rejection(
which: ['is only ${actual.age} years old'],
);
},
);
}
}nesthasnesthashasextension CustomPersonChecks on Subject<Person> {
Subject<Address> get address => has((p) => p.address, 'address');
}nestextension CustomPersonChecks on Subject<Person> {
Subject<String> get ssn => context.nest(
'has a valid SSN',
(actual) {
final ssnValue = actual.ssn;
if (ssnValue == null) {
return Extracted.rejection(which: ['has no SSN']);
}
return Extracted.value(ssnValue);
},
);
}context.expectAsynccontext.nestAsyncFutureextension CustomFutureChecks<T> on Subject<Future<T>> {
Future<void> completesNormally() {
return context.expectAsync(
() => ['completes without throwing'],
(actual) async {
try {
await actual;
return null; // Pass
} catch (e) {
return Rejection(which: ['threw $e']);
}
},
);
}
}# 1. Find all test files containing legacy expect() or expectLater()
grep -rn "expect(" test/
grep -rn "expectLater(" test/
# 2. Find potential collection equality pitfalls (literal lists or maps)
grep -rn "expect(.*, \[" test/
grep -rn "expect(.*, {" test/
# 3. Find matches() calls (need conversion to RegExp + matchesPattern)
grep -rn "matches(" test/
# 4. Find legacy TypeMatcher.having() calls (which need conversion to .has())
grep -rn "having(" test/expect(someValue, isNotNull);
expect(result, isTrue, reason: 'should be successful');
expect(myString, startsWith('hello'));check(someValue).isNotNull();
check(because: 'should be successful', result).isTrue();
check(myString).startsWith('hello');expect(items, [1, 2, 3]);
expect(configMap, equals({'port': 8080}));check(items).deepEquals([1, 2, 3]);
check(configMap).deepEquals({'port': 8080});expect(someString, allOf([
startsWith('a'),
contains('b'),
endsWith('c'),
]));check(someString)
..startsWith('a')
..contains('b')
..endsWith('c');expect(response, isA<Response>()
.having((r) => r.statusCode, 'statusCode', 200)
.having((r) => r.body, 'body', contains('success')));check(response).isA<Response>()
..has((r) => r.statusCode, 'statusCode').equals(200)
..has((r) => r.body, 'body').contains('success');expect(fetchData(), completes);
expect(fetchData(), completion(equals('data')));
expect(failingCall(), throwsA(isA<StateError>()));await check(fetchData()).completes();
await check(fetchData()).completes((it) => it.equals('data'));
await check(failingCall()).throws<StateError>();var queue = StreamQueue(Stream.fromIterable([1, 2, 3]));
await expectLater(queue, emitsInOrder([1, 2, 3]));var queue = StreamQueue(Stream.fromIterable([1, 2, 3]));
await check(queue).inOrder([
(s) => s.emits((e) => e.equals(1)),
(s) => s.emits((e) => e.equals(2)),
(s) => s.emits((e) => e.equals(3)),
]);