dart-migrate-to-checks-package
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMigrating Dart Tests to Package Checks
将 Dart 测试迁移至 Package Checks
Use this skill when you need to migrate a Dart test suite from the legacy
(which is exported by default from )
to the modern, type-safe, and literate assertion library.
package:matcherpackage:test/test.dartpackage:checks当你需要将 Dart 测试套件从传统的 (默认从 导出)迁移至现代、类型安全且可读性强的 断言库时,可以使用此技能。
package:matcherpackage:test/test.dartpackage:checksContents
目录
When to Use This Skill
何时使用此技能
- When asked to "migrate tests to checks", "use package:checks", or "modernize test assertions".
- When updating legacy test suites where static type safety, better autocomplete in IDEs, and highly detailed failure diagnostics are desired.
- 当被要求“将测试迁移至 checks”、“使用 package:checks”或“现代化测试断言”时。
- 当更新传统测试套件,需要静态类型安全、IDE 中更好的自动补全以及更详细的失败诊断时。
How to Use This Skill (The Workflow)
如何使用此技能(工作流程)
Follow this structured workflow to safely and systematically migrate a test suite:
遵循以下结构化工作流程,安全且系统地迁移测试套件:
1. Dependency Setup
1. 依赖配置
- Add as a
package:checksindev_dependency:pubspec.yamlbashdart pub add dev:checks - Remove if it is explicitly listed under
package:matcher(it is typically transitively included bydev_dependencies, which is fine).package:test
- 在 中将
pubspec.yaml添加为package:checks:dev_dependencybashdart pub add dev:checks - 如果 被显式列在
package:matcher下,请将其移除(它通常由dev_dependencies间接引入,这种情况无需处理)。package:test
2. Identify and Plan Target Files
2. 识别并规划目标文件
- Use the grep patterns in Strategies for Discovery
to locate all test files containing legacy or
expectcalls.expectLater - Decide whether to migrate files fully or incrementally.
- 使用发现策略中的 grep 模式定位所有包含传统 或
expect调用的测试文件。expectLater - 决定是完全迁移文件还是增量迁移。
3. Migrating a File (Incremental or Full)
3. 迁移文件(增量或完全)
For any target test file:
- Update Imports:
- Replace the generic with:
import 'package:test/test.dart';dartimport 'package:test/scaffolding.dart'; import 'package:checks/checks.dart'; - For Incremental Migration: If you only want to migrate some test cases
in the file, or want to migrate one step at a time, add:
dart
import 'package:test/expect.dart'; // Temporarily allows legacy expect()
- Replace the generic
- Translate Assertions: Rewrite legacy and
expectcalls toexpectLatersyntax following the Key Syntax Differences and Pitfalls and the Matcher-to-Checks Mapping Table.check - Verify via Compiler: If migrating fully, remove the line. Any remaining un-migrated
import 'package:test/expect.dart';calls will immediately surface as compiler errors, making them easy to find and fix.expect
对于任意目标测试文件:
- 更新导入:
- 将通用的 替换为:
import 'package:test/test.dart';dartimport 'package:test/scaffolding.dart'; import 'package:checks/checks.dart'; - 增量迁移:如果你只想迁移文件中的部分测试用例,或者想分步迁移,请添加:
dart
import 'package:test/expect.dart'; // 临时允许使用传统 expect()
- 将通用的
- 转换断言:遵循关键语法差异与陷阱和Matcher 到 Checks 的映射表,将传统的 和
expect调用重写为expectLater语法。check - 通过编译器验证:如果是完全迁移,请移除 行。任何未迁移的
import 'package:test/expect.dart';调用都会立即触发编译器错误,便于查找和修复。expect
4. Verification and Feedback Loops
4. 验证与反馈循环
- Static Analysis: Run static analysis on the target package:
Pay close attention to generic type parameters onbash
dart analyzeand ensure asynchronous expectations are properly awaited (check for.isA<Type>()warnings).unawaited_futures - Run Tests: Execute the tests to verify both behavior and correct
assertion runtime logic:
If a test fails, review the extremely detailed failure output ofbash
dart testto diagnose if the test is genuinely failing or if the expectation was translated incorrectly.package:checks
- 静态分析:对目标包运行静态分析:
特别注意bash
dart analyze上的泛型类型参数,并确保异步断言已正确等待(检查.isA<Type>()警告)。unawaited_futures - 运行测试:执行测试以验证行为和断言运行时逻辑是否正确:
如果测试失败,请查看bash
dart test极其详细的失败输出,以诊断测试是真的失败还是期望转换错误。package:checks
Key Syntax Differences and Pitfalls
关键语法差异与陷阱
[!IMPORTANT] A line-for-line translation can sometimes introduce subtle bugs or false passes. Always review these key differences carefully:
[!IMPORTANT] 逐行转换有时会引入细微的 bug 或误判。请务必仔细查看以下关键差异:
1. Collection Equality Pitfall (equals
vs deepEquals
)
equalsdeepEquals1. 集合相等陷阱(equals
vs deepEquals
)
equalsdeepEquals- Legacy Matcher: or
expect(actual, expected)performed a deep equality check if the arguments were collections (Lists, Maps, Sets).expect(actual, equals(expected)) - Package Checks: corresponds strictly to
.equals(expected). Since Dart collections do not overrideoperator ==for element-wise comparison, usingoperator ==on a collection will check for identity and almost certainly fail at runtime..equals - Remediation: You must replace collection equality assertions with
.
.deepEquals(expected)dart// BEFORE (Matcher) expect(myList, [1, 2, 3]); // AFTER (Checks) check(myList).deepEquals([1, 2, 3]);
- 传统 Matcher:或
expect(actual, expected)在参数为集合(列表、映射、集合)时会执行深度相等检查。expect(actual, equals(expected)) - Package Checks:严格对应
.equals(expected)。由于 Dart 集合未针对元素级比较重写operator ==,因此对集合使用operator ==会检查同一性,几乎肯定会在运行时失败。.equals - 修复方案:必须将集合相等断言替换为 。
.deepEquals(expected)dart// 之前(Matcher) expect(myList, [1, 2, 3]); // 之后(Checks) check(myList).deepEquals([1, 2, 3]);
2. The reason
Parameter is now because
reasonbecause2. reason
参数变为 because
reasonbecause- Legacy Matcher: The explanation was passed as a trailing named
argument to
reason:expectdartexpect(actual, expectation, reason: 'Explanation'); - Package Checks: The explanation is passed as the named argument
to the
becausefunction before the actual subject:checkdartcheck(because: 'Explanation', actual).expectation();
- 传统 Matcher:解释作为尾随命名参数 传递给
reason:expectdartexpect(actual, expectation, reason: 'Explanation'); - Package Checks:解释作为命名参数 传递给
because函数,且位于实际检查对象之前:checkdartcheck(because: 'Explanation', actual).expectation();
3. Regular Expression Matching (matches
vs matchesPattern
)
matchesmatchesPattern3. 正则表达式匹配(matches
vs matchesPattern
)
matchesmatchesPattern- Legacy Matcher: The matcher automatically converted a
matches(pattern)argument into aString(e.g.,RegExpmatchedmatches(r'\d')).'1' - Package Checks: treats a
.matchesPattern(pattern)argument as a literal string pattern.String - Remediation: To match using a regular expression, you must explicitly
pass a object:
RegExpdart// BEFORE (Matcher) expect(someString, matches(r'\d+')); // AFTER (Checks) check(someString).matchesPattern(RegExp(r'\d+'));
- 传统 Matcher:匹配器会自动将
matches(pattern)参数转换为String(例如,RegExp匹配matches(r'\d'))。'1' - Package Checks:将
.matchesPattern(pattern)参数视为字面字符串模式。String - 修复方案:要使用正则表达式进行匹配,必须显式传递 对象:
RegExpdart// 之前(Matcher) expect(someString, matches(r'\d+')); // 之后(Checks) check(someString).matchesPattern(RegExp(r'\d+'));
4. Property Extraction (TypeMatcher.having
vs .has
)
TypeMatcher.having.has4. 属性提取(TypeMatcher.having
vs .has
)
TypeMatcher.having.has- Legacy Matcher: Chained field/property expectations used
:
TypeMatcher.having(feature, description, matcher)dartexpect(actual, isA<Person>().having((p) => p.name, 'name', startsWith('A'))); - Package Checks: The extension is available on all
.has(feature, description)s, takes one fewer argument, and returns a newSubjectrepresenting that property. You chain expectations directly off it:Subjectdartcheck(actual).isA<Person>().has((p) => p.name, 'name').startsWith('A');
- 传统 Matcher:链式字段/属性期望使用 :
TypeMatcher.having(feature, description, matcher)dartexpect(actual, isA<Person>().having((p) => p.name, 'name', startsWith('A'))); - Package Checks:扩展方法适用于所有
.has(feature, description),少一个参数,并返回表示该属性的新Subject。你可以直接在其上链式调用期望:Subjectdartcheck(actual).isA<Person>().has((p) => p.name, 'name').startsWith('A');
5. Synchronous vs. Asynchronous throws
throws5. 同步与异步 throws
throws- Legacy Matcher: In ,
package:matcherbehaved similarly for both synchronous closures and asynchronous futures when wrapped inthrowsAorexpect.expectLater - Package Checks: The expectation behaves differently and has different return types depending on whether the subject is synchronous or asynchronous:
.throws<E>()- Synchronous ():
Subject<T Function()>returns a.throws<E>()synchronously. This does not accept a callback argument! You chain or cascade expectations directly off the returnedSubject<E>:Subject<E>dart// 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! - Asynchronous ():
Subject<Future<T>>returns.throws<E>(). Because you cannot chain directly off aFuture<void>, this requires an inspection callback:Future<void>dart// YES (Asynchronous callback) await check(triggerAsyncError()).throws<ArgumentError>((it) => it ..has((e) => e.message, 'message').equals('invalid input')); - Crucial Pitfall: Trying to chain expectations directly after an awaited
asynchronous (e.g.,
.throws<E>()) will fail to compile because it returnsawait check(future).throws<E>().equals(...).Future<void>
- Synchronous (
- 传统 Matcher:在 中,
package:matcher对同步闭包和异步 future 的行为在throwsA或expect中类似。expectLater - Package Checks:期望的行为和返回类型取决于检查对象是同步还是异步:
.throws<E>()- 同步():
Subject<T Function()>同步返回.throws<E>()。这不接受回调参数!你可以直接在返回的Subject<E>上链式或级联调用期望:Subject<E>dart// 正确(同步链式调用) check(() => triggerSyncError()).throws<ArgumentError>() ..has((e) => e.message, 'message').equals('invalid input'); // 错误(向同步 throws 传递回调会导致编译器错误!) check(() => triggerSyncError()).throws<ArgumentError>((it) => ...); // ERROR! - 异步():
Subject<Future<T>>返回.throws<E>()。由于无法直接在Future<void>上链式调用,因此需要检查回调:Future<void>dart// 正确(异步回调) 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>
- 同步(
6. RegExp / Pattern Equality
6. RegExp / Pattern 相等性
- Legacy Matcher: In ,
package:matcherexpect(myPattern,worked because the matcher comparison rules handled RegExp instances.equals(RegExp('Hello'))) - Package Checks: uses strict Dart
.equals()equality. Since separate==instances do not satisfyRegExp, using==will fail at runtime..equals() - Remediation: Use type refinement along with cascades to assert on the properties of the
.isA<RegExp>()object explicitly:RegExpdartcheck(myPattern).isA<RegExp>() ..has((r) => r.pattern, 'pattern').equals('Hello') ..has((r) => r.isMultiLine, 'isMultiLine').isTrue();
- 传统 Matcher:在 中,
package:matcher可以正常工作,因为匹配器比较规则处理了 RegExp 实例。expect(myPattern, equals(RegExp('Hello'))) - Package Checks:使用严格的 Dart
.equals()相等性。由于不同的==实例不满足RegExp,使用==会在运行时失败。.equals() - 修复方案:使用 类型细化,并结合级联操作显式断言
.isA<RegExp>()对象的属性:RegExpdartcheck(myPattern).isA<RegExp>() ..has((r) => r.pattern, 'pattern').equals('Hello') ..has((r) => r.isMultiLine, 'isMultiLine').isTrue();
7. Strict Nullable Boolean Safety (bool?
fields)
bool?7. 严格可空布尔安全性(bool?
字段)
bool?- Legacy Matcher: Statically, and
isTrueperformed loose dynamic checks at runtime, which silently accepted nullable booleans (isFalse).bool? - Package Checks: and
.isTrue()are defined strictly on.isFalse()(non-nullable). They are not available onSubject<bool>.Subject<bool?> - Remediation: For fields declared as , you must either refine the subject (e.g.,
bool?) or simply use.isNotNull().isTrue()and.equals(true)which are generic and work on all types:.equals(false)dart// If options.flagOutdated is a bool? check(options.flagOutdated).equals(true); check(options.flagOutdated).equals(false);
- 传统 Matcher:在静态层面,和
isTrue在运行时执行松散的动态检查,会静默接受可空布尔值(isFalse)。bool? - Package Checks:和
.isTrue()严格定义在.isFalse()(非可空)上。它们不适用于Subject<bool>。Subject<bool?> - 修复方案:对于声明为 的字段,你必须细化检查对象(例如,
bool?),或者直接使用.isNotNull().isTrue()和.equals(true),它们是泛型的,适用于所有类型:.equals(false)dart// 如果 options.flagOutdated 是 bool? check(options.flagOutdated).equals(true); check(options.flagOutdated).equals(false);
8. Map Key Containment (containsKey
vs contains
)
containsKeycontains8. 映射键包含(containsKey
vs contains
)
containsKeycontains- Legacy Matcher: In ,
package:matcherwas used to assert that acontains(key)contained a specific key.Map - Package Checks: Calling on a
.contains(...)is not defined and will fail compilation.Subject<Map> - Remediation: Use the map-specific matcher instead:
.containsKey(key)dart// BEFORE (Matcher) expect(myMap, contains('my_key')); // AFTER (Checks) check(myMap).containsKey('my_key');
- 传统 Matcher:在 中,
package:matcher用于断言contains(key)包含特定键。Map - Package Checks:对 调用
Subject<Map>未定义,会编译失败。.contains(...) - 修复方案:改用映射专用的 匹配器:
.containsKey(key)dart// 之前(Matcher) expect(myMap, contains('my_key')); // 之后(Checks) check(myMap).containsKey('my_key');
9. Explicit Generic Parameters for Extension Types
9. 扩展类型的显式泛型参数
- Legacy Matcher: compiled because of loose dynamic equality.
expect(extensionTypeConst, 3) - Package Checks: If is an extension type representation of
QrEciValue(e.g.,int), callingextension type const QrEciValue(int value) implements inton a.equals(3)fails becauseSubject<QrEciValue>(an3) is not assignable toint. Casting withQrEciValuewill trigger an "Unnecessary cast" static analysis warning becauseas intstatically implementsQrEciValue.int - Remediation: Explicitly specify the generic type parameter on the function to force checks to treat it as the primitive type:
checkdart// YES (Type-safe and warning-free) check<int>(QrEciValue.iso8859_1).equals(3);
- 传统 Matcher:可以编译,因为松散的动态相等性。
expect(extensionTypeConst, 3) - Package Checks:如果 是
QrEciValue的扩展类型表示(例如,int),对extension type const QrEciValue(int value) implements int调用Subject<QrEciValue>会失败,因为.equals(3)(3类型)无法赋值给int。使用QrEciValue进行强制转换会触发“不必要的强制转换”静态分析警告,因为as int在静态层面实现了QrEciValue。int - 修复方案:在 函数上显式指定泛型类型参数,强制检查将其视为原始类型:
checkdart// 正确(类型安全且无警告) check<int>(QrEciValue.iso8859_1).equals(3);
10. Dynamic Map / JSON Lookup Casting
10. 动态映射 / JSON 查找强制转换
- Legacy Matcher: Loose dynamic typing allowed comparing nested json lookups
statically typed as directly against lists or maps.
dynamic - Package Checks: Strict type safety rejects the implicit assignment of
to
dynamicinIterable<Object?>..deepEquals(...) - Remediation: Statically cast the dynamic lookup result to a or
List:Mapdart// YES (Explicit cast to List) check(myIterable).deepEquals(json['data']['items'] as List);
- 传统 Matcher:松散的动态类型允许将静态类型为 的嵌套 JSON 查找结果直接与列表或映射比较。
dynamic - Package Checks:严格的类型安全性拒绝将 隐式赋值给
dynamic中的.deepEquals(...)。Iterable<Object?> - 修复方案:将动态查找结果静态强制转换为 或
List:Mapdart// 正确(显式强制转换为 List) check(myIterable).deepEquals(json['data']['items'] as List);
Matcher-to-Checks Mapping Table
Matcher 到 Checks 的映射表
Use this table as a quick reference for direct matcher replacements:
| 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 |
| |
使用此表快速查找直接的 matcher 替代方案:
| 传统 Matcher | Package Checks 等效方案 | 说明 |
|---|---|---|
| | 集合请使用 |
| | 集合请使用 |
| | 直接支持链式调用 |
| | 验证同一性 |
| | 示例: |
| | 示例: |
| | 适用于字符串、映射、可迭代对象等 |
| | 示例: |
| | 适用于字符串、可迭代对象(映射请使用 |
| | 映射键包含 |
| | 仅适用于字符串 |
| | 仅适用于字符串 |
| | 适用于字符串、映射、可迭代对象 |
| | 适用于字符串、映射、可迭代对象 |
| | |
| | |
| | 仅适用于非可空 |
| | 仅适用于非可空 |
| | 必须等待! |
| | 必须等待! |
| | 必须等待! |
| | 必须等待! |
| | 仅适用于字符串 |
| |
Matchers with No Direct Replacements
无直接替代方案的 Matcher
Some legacy matchers do not have a one-to-one equivalent in
due to API cleanup. Use these standard workarounds:
package:checks部分传统 matcher 在 中没有一对一的等效方案,这是由于 API 精简导致的。请使用以下标准解决方法:
package:checks1. Specific Error Matchers
1. 特定错误匹配器
- Legacy: ,
throwsArgumentError,throwsStateError, etc.throwsUnsupportedError - Checks: Use with the specific error type:
.throws<T>()dartawait check(triggerError()).throws<ArgumentError>();
- 传统:、
throwsArgumentError、throwsStateError等。throwsUnsupportedError - Checks:使用 并指定特定错误类型:
.throws<T>()dartawait check(triggerError()).throws<ArgumentError>();
2. The anything
Matcher
anything2. anything
匹配器
anything- Legacy:
expect(actual, anything) - Checks: Pass an empty condition callback when a condition is syntactically required:
(_) {}dartawait check(someFuture).completes((_) {});
- 传统:
expect(actual, anything) - Checks:当语法上需要条件时,传递空条件回调 :
(_) {}dartawait check(someFuture).completes((_) {});
3. Specific Numeric Toggles
3. 特定数值判断
- Legacy: ,
isPositive,isNegative,isZero,isNonPositive,isNonNegativeisNonZero - Checks: Use explicit comparative expectations:
- $\rightarrow$
isPositiveisGreaterThan(0) - $\rightarrow$
isNegativeisLessThan(0) - $\rightarrow$
isZeroequals(0) - $\rightarrow$
isNonNegativeisGreaterOrEqual(0)
- 传统:、
isPositive、isNegative、isZero、isNonPositive、isNonNegativeisNonZero - Checks:使用显式的比较期望:
- →
isPositiveisGreaterThan(0) - →
isNegativeisLessThan(0) - →
isZeroequals(0) - →
isNonNegativeisGreaterOrEqual(0)
4. Numeric Ranges
4. 数值范围
- Legacy: ,
inClosedOpenRange(min, max), etc.inInclusiveRange(min, max) - Checks: Chain the boundaries using the cascade operator ():
..dartcheck(actualValue) ..isGreaterOrEqual(min) ..isLessThan(max);
- 传统:、
inClosedOpenRange(min, max)等。inInclusiveRange(min, max) - Checks:使用级联运算符()链式调用边界条件:
..dartcheck(actualValue) ..isGreaterOrEqual(min) ..isLessThan(max);
Writing Custom Expectations (Replacing Custom Matchers)
编写自定义期望(替换自定义 Matcher)
When migrating from a legacy codebase, you may encounter custom
subclasses. In , custom assertions are implemented as
methods on .
Matcherpackage:checksextensionSubject<T>To write custom expectations, you must import the checks context API:
dart
import 'package:checks/context.dart';从传统代码库迁移时,你可能会遇到自定义 子类。在 中,自定义断言通过 上的 方法实现。
Matcherpackage:checksSubject<T>extension要编写自定义期望,必须导入 checks 上下文 API:
dart
import 'package:checks/context.dart';1. Simple Custom Expectations (using expect
)
expect1. 简单自定义期望(使用 expect
)
expectUse to check a property and return a on failure:
context.expectRejectiondart
extension 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'],
);
},
);
}
}使用 检查属性,并在失败时返回 :
context.expectRejectiondart
extension CustomPersonChecks on Subject<Person> {
void isAdult() {
context.expect(
() => ['is an adult (age >= 18)'],
(actual) {
if (actual.age >= 18) return null; // 通过
return Rejection(
which: ['is only ${actual.age} years old'],
);
},
);
}
}2. Nested Property Extraction (using nest
or has
)
nesthas2. 嵌套属性提取(使用 nest
或 has
)
nesthasTo extract a property and allow further chained checks, use or the
simpler helper:
nesthas- Using (Recommended for simple, non-failing field access):
hasdartextension CustomPersonChecks on Subject<Person> { Subject<Address> get address => has((p) => p.address, 'address'); } - Using (For property extraction that can fail or reject):
nestdartextension 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); }, ); }
要提取属性并允许进一步的链式检查,请使用 或更简单的 助手:
nesthas- 使用 (推荐用于简单、不会失败的字段访问):
hasdartextension CustomPersonChecks on Subject<Person> { Subject<Address> get address => has((p) => p.address, 'address'); } - 使用 (用于可能失败或拒绝的属性提取):
nestdartextension 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); }, ); }
3. Asynchronous Custom Expectations
3. 异步自定义期望
If the expectation is asynchronous (e.g. checking a Future or Stream), use
or and return the resulting :
context.expectAsynccontext.nestAsyncFuturedart
extension 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']);
}
},
);
}
}如果期望是异步的(例如检查 Future 或 Stream),请使用 或 并返回结果 :
context.expectAsynccontext.nestAsyncFuturedart
extension CustomFutureChecks<T> on Subject<Future<T>> {
Future<void> completesNormally() {
return context.expectAsync(
() => ['completes without throwing'],
(actual) async {
try {
await actual;
return null; // 通过
} catch (e) {
return Rejection(which: ['threw $e']);
}
},
);
}
}Strategies for Discovery
发现策略
Execute these commands in the terminal to identify legacy matchers and files
requiring migration:
bash
undefined在终端中执行以下命令,识别需要迁移的传统 matcher 和文件:
bash
undefined1. Find all test files containing legacy expect() or expectLater()
1. 查找所有包含传统 expect() 或 expectLater() 的测试文件
grep -rn "expect(" test/
grep -rn "expectLater(" test/
grep -rn "expect(" test/
grep -rn "expectLater(" test/
2. Find potential collection equality pitfalls (literal lists or maps)
2. 查找潜在的集合相等陷阱(字面量列表或映射)
grep -rn "expect(., [" test/
grep -rn "expect(., {" test/
grep -rn "expect(., [" test/
grep -rn "expect(., {" test/
3. Find matches() calls (need conversion to RegExp + matchesPattern)
3. 查找 matches() 调用(需要转换为 RegExp + matchesPattern)
grep -rn "matches(" test/
grep -rn "matches(" test/
4. Find legacy TypeMatcher.having() calls (which need conversion to .has())
4. 查找传统 TypeMatcher.having() 调用(需要转换为 .has())
grep -rn "having(" test/
---grep -rn "having(" test/
---Examples
示例
Basic Assertions
基础断言
Before (Matcher):
dart
expect(someValue, isNotNull);
expect(result, isTrue, reason: 'should be successful');
expect(myString, startsWith('hello'));After (Checks):
dart
check(someValue).isNotNull();
check(because: 'should be successful', result).isTrue();
check(myString).startsWith('hello');之前(Matcher):
dart
expect(someValue, isNotNull);
expect(result, isTrue, reason: 'should be successful');
expect(myString, startsWith('hello'));之后(Checks):
dart
check(someValue).isNotNull();
check(because: 'should be successful', result).isTrue();
check(myString).startsWith('hello');Collection and Deep Equality
集合与深度相等
Before (Matcher):
dart
expect(items, [1, 2, 3]);
expect(configMap, equals({'port': 8080}));After (Checks):
dart
check(items).deepEquals([1, 2, 3]);
check(configMap).deepEquals({'port': 8080});之前(Matcher):
dart
expect(items, [1, 2, 3]);
expect(configMap, equals({'port': 8080}));之后(Checks):
dart
check(items).deepEquals([1, 2, 3]);
check(configMap).deepEquals({'port': 8080});Chaining and Cascades
链式调用与级联
Before (Matcher):
dart
expect(someString, allOf([
startsWith('a'),
contains('b'),
endsWith('c'),
]));After (Checks):
dart
check(someString)
..startsWith('a')
..contains('b')
..endsWith('c');之前(Matcher):
dart
expect(someString, allOf([
startsWith('a'),
contains('b'),
endsWith('c'),
]));之后(Checks):
dart
check(someString)
..startsWith('a')
..contains('b')
..endsWith('c');Complex Property Matching (has)
复杂属性匹配(has)
Before (Matcher):
dart
expect(response, isA<Response>()
.having((r) => r.statusCode, 'statusCode', 200)
.having((r) => r.body, 'body', contains('success')));After (Checks):
dart
check(response).isA<Response>()
..has((r) => r.statusCode, 'statusCode').equals(200)
..has((r) => r.body, 'body').contains('success');之前(Matcher):
dart
expect(response, isA<Response>()
.having((r) => r.statusCode, 'statusCode', 200)
.having((r) => r.body, 'body', contains('success')));之后(Checks):
dart
check(response).isA<Response>()
..has((r) => r.statusCode, 'statusCode').equals(200)
..has((r) => r.body, 'body').contains('success');Asynchronous Futures
异步 Future
Before (Matcher):
dart
expect(fetchData(), completes);
expect(fetchData(), completion(equals('data')));
expect(failingCall(), throwsA(isA<StateError>()));After (Checks):
dart
await check(fetchData()).completes();
await check(fetchData()).completes((it) => it.equals('data'));
await check(failingCall()).throws<StateError>();之前(Matcher):
dart
expect(fetchData(), completes);
expect(fetchData(), completion(equals('data')));
expect(failingCall(), throwsA(isA<StateError>()));之后(Checks):
dart
await check(fetchData()).completes();
await check(fetchData()).completes((it) => it.equals('data'));
await check(failingCall()).throws<StateError>();Asynchronous Streams
异步 Stream
Before (Matcher):
dart
var queue = StreamQueue(Stream.fromIterable([1, 2, 3]));
await expectLater(queue, emitsInOrder([1, 2, 3]));After (Checks):
dart
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)),
]);之前(Matcher):
dart
var queue = StreamQueue(Stream.fromIterable([1, 2, 3]));
await expectLater(queue, emitsInOrder([1, 2, 3]));之后(Checks):
dart
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)),
]);