Officialflutter/agent-plugins1 file

Dart Migrate To Checks Package

|-

Specification
Skill ID
flutter/agent-plugins/dart-migrate-to-checks-package
Publisher
flutter
Repository
agent-plugins
Installs
204
Files
1
Synced
Sep 24, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

flutter/agent-plugins/dart-migrate-to-checks-packageInstalls these files
  • SKILL.md

What this skill tells the agent

Migrating Dart Tests to Package Checks

Use this skill when you need to migrate a Dart test suite from the legacy package:matcher (which is exported by default from package:test/test.dart) to the modern, type-safe, and literate package:checks assertion library.

Contents

  • When to Use This Skill
  • How to Use This Skill (The Workflow)
  • Key Syntax Differences and Pitfalls
  • Matcher-to-Checks Mapping Table
  • Matchers with No Direct Replacements
  • Strategies for Discovery
  • Examples

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.

How to Use This Skill (The Workflow)

Follow this structured workflow to safely and systematically migrate a test suite:

1. Dependency Setup

  • Add package:checks as a dev_dependency in pubspec.yaml: ``bash dart pub add dev:checks ``
  • Remove package:matcher if it is explicitly listed under dev_dependencies (it is typically transitively included by package:test, which is fine).

2. Identify and Plan Target Files

  • Use the grep patterns in Strategies for Discovery to locate all test files containing legacy expect or expectLater calls.
  • Decide whether to migrate files fully or incrementally.

3. Migrating a File (Incremental or Full)

For any target test file:

  1. Update Imports:
  • Replace the generic import 'package:test/test.dart'; with: ``dart import '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() ``
  1. Translate Assertions: Rewrite legacy expect and expectLater calls to check syntax following the Key Syntax Differences and Pitfalls and the Matcher-to-Checks Mapping Table.
  2. Verify via Compiler: If migrating fully, remove the import 'package:test/expect.dart'; line. Any remaining un-migrated expect calls will immediately surface as compiler errors, making them easy to find and fix.

4. Verification and Feedback Loops

  • Static Analysis: Run static analysis on the target package: ``bash dart analyze ` Pay close attention to generic type parameters on .isA<Type>() and ensure asynchronous expectations are properly awaited (check for unawaited_futures` warnings).
  • Run Tests: Execute the tests to verify both behavior and correct assertion runtime logic: ``bash dart test ` If a test fails, review the extremely detailed failure output of package:checks` to diagnose if the test is genuinely failing or if the expectation was translated incorrectly.

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:

1. Collection Equality Pitfall (equals vs deepEquals)

  • Legacy Matcher: expect(actual, expected) or expect(actual, equals(expected)) performed a deep equality check if the arguments were collections (Lists, Maps, Sets).
  • Package Checks: .equals(expected) corresponds strictly to operator ==. Since Dart collections do not override operator == for element-wise comparison, using .equals on a collection will check for identity and almost certainly fail at runtime.
  • 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]);


### 2. The `reason` Parameter is now `because`
- **Legacy Matcher**: The explanation was passed as a trailing named
  argument `reason` to `expect`:

expect(actual, expectation, reason: 'Explanation');

- **Package Checks**: The explanation is passed as the named argument
  `because` to the `check` function *before* the actual subject:

check(because: 'Explanation', actual).expectation();


### 3. Regular Expression Matching (`matches` vs `matchesPattern`)
- **Legacy Matcher**: The `matches(pattern)` matcher automatically converted
  a `String` argument into a `RegExp` (e.g., `matches(r'\d')` matched `'1'`).
- **Package Checks**: `.matchesPattern(pattern)` treats a `String` argument
  as a literal string pattern.
- **Remediation**: To match using a regular expression, you must explicitly
  pass a `RegExp` object:

// BEFORE (Matcher) expect(someString, matches(r'\d+'));

// AFTER (Checks) check(someString).matchesPattern(RegExp(r'\d+'));


### 4. Property Extraction (`TypeMatcher.having` vs `.has`)
- **Legacy Matcher**: Chained field/property expectations used
  `TypeMatcher.having(feature, description, matcher)`:

expect(actual, isA<Person>().having((p) => p.name, 'name', startsWith('A')));

- **Package Checks**: The `.has(feature, description)` extension is
  available on all `Subject`s, takes one fewer argument, and returns a new
  `Subject` representing that property. You chain expectations directly off
  it:

check(actual).isA<Person>().has((p) => p.name, 'name').startsWith('A');


### 5. Synchronous vs. Asynchronous `throws`
- **Legacy Matcher**: In `package:matcher`, `throwsA` behaved similarly for both
  synchronous closures and asynchronous futures when wrapped in `expect` or
  `expectLater`.
- **Package Checks**: The `.throws<E>()` expectation behaves differently and
  has different return types depending on whether the subject is synchronous or
  asynchronous:
  - **Synchronous** (`Subject<T Function()>`): `.throws<E>()` returns a
    `Subject<E>` synchronously. This **does not** accept a callback argument!
    You chain or cascade expectations directly off the returned `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!

  - **Asynchronous** (`Subject<Future<T>>`): `.throws<E>()` returns
    `Future<void>`. Because you cannot chain directly off a `Future<void>`, this
    **requires** an inspection callback:

// 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 `.throws<E>()` (e.g.,
    `await check(future).throws<E>().equals(...)`) will fail to compile
    because it returns `Future<void>`.

### 6. RegExp / Pattern Equality
- **Legacy Matcher**: In `package:matcher`, `expect(myPattern,`
  `equals(RegExp('Hello')))` worked because the matcher comparison rules
  handled RegExp instances.
- **Package Checks**: `.equals()` uses strict Dart `==` equality. Since separate
  `RegExp` instances do not satisfy `==`, using `.equals()` will fail at runtime.
- **Remediation**: Use `.isA<RegExp>()` type refinement along with cascades to
  assert on the properties of the `RegExp` object explicitly:

check(myPattern).isA<RegExp>() ..has((r) => r.pattern, 'pattern').equals('Hello')

Use this skill in a project

Open any RiverX project, search the Skills panel for flutter/agent-plugins/dart-migrate-to-checks-package, and install it in one click.

Use this skill