| ## Migrating from package:matcher | |
| `package:checks` is currently in preview. Once this package reaches a stable | |
| version, it will be the recommended package by the Dart team to use for most | |
| tests. | |
| [`package:matcher`][matcher] is the legacy package with an API exported from | |
| `package:test/test.dart` and `package:test/expect.dart`. | |
| **Do I have to migrate all at once?** No. `package:matcher` will be compatible | |
| with `package:checks`, and old tests can continue to use matchers. Test cases | |
| within the same file can use a mix of `expect` and `check`. | |
| **_Should_ I migrate all at once?** Probably not, it depends on your tolerance | |
| for having tests use a mix of APIs. As you add new tests, or need to make | |
| updates to existing tests, using `checks` will make testing easier. Tests which | |
| are stable and passing will not get significant benefits from a migration. | |
| **Do I need to migrate at all?** No. When `package:test`stops exporting | |
| these members it will be possible to add a dependency on `package:matcher` and | |
| continue to use them. `package:matcher` will continue to be available. | |
| **Why is the Dart team adding a second framework?** The `matcher` package has a | |
| design which is fundamentally incompatible with using static types to validate | |
| correct use. With an entirely new design, the static types in `checks` give | |
| confidence that the expectation is appropriate for the value, and can narrow | |
| autocomplete choices in the IDE for a better editing experience. The clean break | |
| from the legacy implementation and API also gives an opportunity to make small | |
| behavior and signature changes to align with modern Dart idioms. | |
| **Should I start using checks right away?** There is still a | |
| high potential for minor or major breaking changes during the preview window. | |
| Once this package is stable, yes! The experience of using `checks` improves on | |
| `matcher`. See some of the [improvements to look forward to in checks | |
| below](#improvements-you-can-expect). | |
| [matcher]: https://pub.dev/packages/matcher | |
| ## Trying Checks as a Preview | |
| 1. Add a `dev_dependency` on `checks: ^0.2.0`. | |
| 1. Replace the existing `package:test/test.dart` import with | |
| `package:test/scaffolding.dart`. | |
| 1. Add an import to `package:checks/checks.dart`. | |
| 1. For an incremental migration within the test, add an import to | |
| `package:test/expect.dart`. Remove it to surface errors in tests that still | |
| need to be migrated, or keep it in so the tests work without being fully | |
| migrated. | |
| 1. Migrate the test cases. | |
| ## Migrating from Matchers | |
| Replace calls to `expect` or `expectLater` with a call to `check` passing the | |
| first argument. | |
| When a direct replacement is available, change the second argument from calling | |
| a function returning a Matcher, to calling the relevant extension method on the | |
| `Subject`. | |
| Whenever you see a bare non-matcher value argument for `expected`, assume it | |
| should use the `equals` expectation, although take care when the subject is a | |
| collection. | |
| See below, `.equals` may not always be the correct replacement in | |
| `package:checks`. | |
| ```dart | |
| expect(actual, expected); | |
| check(actual).equals(expected); | |
| // or maybe | |
| check(actualCollection).deepEquals(expected); | |
| await expectLater(actual, completes()); | |
| await check(actual).completes(); | |
| ``` | |
| If you use the `reason` argument to `expect`, rename it to `because`. | |
| ```dart | |
| expect(actual, expectation(), reason: 'some explanation'); | |
| check(because: 'some explanation', actual).expectation(); | |
| ``` | |
| ### Differences in behavior from matcher | |
| - The `equals` Matcher performed a deep equality check on collections. | |
| `.equals()` expectation will only correspond to [operator ==] so some tests | |
| may need to replace `.equals()` with `.deepEquals()`. | |
| - Streams must be explicitly wrapped into a `StreamQueue` before they can be | |
| tested for behavior. Use `check(actualStream).withQueue`. | |
| - `emitsAnyOf` is `Subject<StreamQueue>.anyOf`. `emitsInOrder` is `inOrder`. | |
| The arguments are `FutureOr<void> Function(Subject<StreamQueue>)` and match | |
| a behavior of the entire stream. In `matcher` the elements to expect could | |
| have been a bare value to check for equality, a matcher for the emitted | |
| value, or a matcher for the entire queue which would match multiple values. | |
| Use `(s) => s.emits((e) => e.interestingCheck())` to check the emitted | |
| elements. | |
| - In `package:matcher` the [`matches` Matcher][matches] converted a `String` | |
| argument into a `Regex`, so `matches(r'\d')` would match the value `'1'`. | |
| This was potentially confusing, because even though `String` is a subtype of | |
| `Pattern`, it wasn't used as a pattern directly. | |
| With `matchesPattern` a `String` argument is used as a `Pattern` and | |
| comparison uses [`String.allMatches`][allMatches]. | |
| For backwards compatibility change `matches(regexString)` to | |
| `matchesPattern(RegExp(regexString))`. | |
| - The `TypeMatcher.having` API is replace by the more general`.has`. While | |
| `.having` could only be called on a `TypeMatcher` using `.isA`, `.has` works | |
| on any `Subject`. `CoreChecks.has` takes 1 fewer arguments - instead of | |
| taking the last argument, a `matcher` to apply to the field, it returns a | |
| `Subject` for the field. | |
| [matches]:https://pub.dev/documentation/matcher/latest/matcher/Matcher/matches.html | |
| [allMatches]:https://api.dart.dev/stable/2.19.1/dart-core/Pattern/allMatches.html | |
| ### Matchers with replacements under a different name | |
| - `anyElement` -> `Subject<Iterable>.any` | |
| - `everyElement` -> `Subject<Iterable>.every` | |
| - `completion(Matcher)` -> `completes(conditionCallback)` | |
| - `containsPair(key, value)` -> Use `Subject<Map>[key].equals(value)` | |
| - `hasLength(expected)` -> `length.equals(expected)` | |
| - `isNot(Matcher)` -> `not(conditionCallback)` | |
| - `pairwiseCompare` -> `pairwiseMatches` | |
| - `same` -> `identicalTo` | |
| - `stringContainsInOrder` -> `Subject<String>.containsInOrder` | |
| - `containsAllInOrder(iterable)` -> | |
| `Subject<Iterable>.containsMatchingInOrder(iterable)` to compare with | |
| conditions other than equals, | |
| `Subject<Iterable>.containsEqualInOrder(iterable)` to compare each index | |
| with the equality operator (`==`). | |
| ### Members from `package:test/expect.dart` without a direct replacement | |
| - `checks` does not ship with any type checking matchers for specific types. | |
| Instead of, for example, `isArgumentError` use `isA<ArgumentError>`, and | |
| similary `throws<ArgumentError>` over `throwsArgumentError`. | |
| - `anything`. When a condition callback is needed that should accept any | |
| value, pass `(_) {}`. | |
| - Specific numeric comparison - `isNegative`, `isPositive`, `isZero` and their | |
| inverses. Use `isLessThan`, `isGreaterThan`, `isLessOrEqual`, and | |
| `isGreaterOrEqual` with appropriate numeric arguments. | |
| - Numeric range comparison, `inClosedOpenRange`, `inExclusiveRange`, | |
| `inInclusiveRange`, `inOpenClosedRange`. Use cascades to chain a check for | |
| both ends of the range onto the same subject. | |
| - `containsOnce`: TODO add missing expectation | |
| - `emitsInAnyOrder`: TODO add missing expectation | |
| - `expectAsync` and `expectAsyncUntil`. Continue to import | |
| `package:test/expect.dart` for these APIs. | |
| - `isIn`: TODO add missing expectation | |
| - `orderedEquals`: Use `deepEquals`. If the equality needs to specifically | |
| *not* be deep equality (this is unusual, nested collections are unlikely to | |
| have a meaningful equality), force using `operator ==` at the first level | |
| with `.deepEquals(expected.map((e) => (Subject<Object?> s) => s.equals(e)))`; | |
| - `prints`: TODO add missing expectation? Is this one worth replacing? | |
| - `predicate`: TODO add missing expectation | |
| ## Improvements you can expect | |
| Expectations are statically restricted to those which are appropriate for the | |
| type. So while the following is statically allowed with `matcher` but always | |
| fails at runtime, the expectation cannot be written at all with `checks`. | |
| ```dart | |
| expect(1, contains(1)); // No static error, always fails | |
| check(1).contains(1); // Static error. The method 'contains' isn't defined | |
| ``` | |
| These static restrictions also improve the relevance of IDE autocomplete | |
| suggestions. While editing with the cursor at `_`, the suggestions provided | |
| in the `matcher` example can include _any_ top level element including matchers | |
| appropriate for other types of value, type names, and top level definitions from | |
| other packages. With the cursor following a `.` in the `checks` example the | |
| suggestions will only be expectations or utilities appropriate for the value | |
| type. | |
| ```dart | |
| expect(actual, _ // many unrelated suggestions | |
| check(actual)._ // specific suggestions | |
| ``` | |
| Asynchronous matchers in `matcher` are a subtype of synchronous matchers, but do | |
| not satisfy the same behavior contract. Some APIs which use a matcher could not | |
| validate whether it would satisfy the behavior it needs, and it could result in | |
| a false success, false failure, or misleading errors. APIs which correctly use | |
| asynchronous matchers need to do a type check and change their interaction based | |
| on the runtime type. Asynchronous expectations in `checks` are refused at | |
| runtime when a synchronous answer is required. The error will help solve the | |
| specific misuse, instead of resulting in a confusing error, or worse a missed | |
| failure. The reason for the poor compatibility in `matcher` is due to some | |
| history of implementation - asynchronous matchers were written in `test` | |
| alongside `expect`, and synchronous matchers have no dependency on the | |
| asynchronous implementation. | |
| Asynchronous expectations always return a `Future`, and with the | |
| [`unawaited_futures` lint][unawaited lint] should more safely ensure that | |
| asynchronous expectation work is completed within the test body. With `matcher` | |
| it was up to the author to correctly use `await expecLater` for asynchronous | |
| cases, and `expect` for synchronous cases, and if `expect` was used with an | |
| asynchronous matcher the expectation could fail at any point. | |
| [unawaited lint]: https://dart.dev/lints/unawaited_futures |