Skip to content

Throw when an async matcher is used with isNot#2663

Draft
natebosch wants to merge 4 commits into
masterfrom
matcher-is-not-sync-only
Draft

Throw when an async matcher is used with isNot#2663
natebosch wants to merge 4 commits into
masterfrom
matcher-is-not-sync-only

Conversation

@natebosch
Copy link
Copy Markdown
Member

Closes #1637

Async matchers are implemented as a hack where they usually appear to
succeed synchronously, and may report an error for the test case later.
This is incompatible with meta matchers or "operator" matchers isNot
and anyOf where the synchronous answer is trusted and other values are
derived from it. In the case of isNot this causes false positives, and
for anyOf it causes false negatives.

Historically it was infeasible to add knowledg about AsyncMatcher in
the definition of these operators, because async matching was
implemented in the test runner directly and the matchers exposed by the
test runner are a superset of those defined by the matcher package. Now
that expect and the async matchers are definied in package:matcher
it is possible to detect when an async matcher is used this way.

There is one async matcher when does often serve as a synchronous
matcher in practice - the throwsA matcher is used for both synchronous
and asynchronous errors, the same matcher is uesed for Function(),
Future Function() and Future values. I think the combination of
throwsA and isNot or anyOf is likely to be rare, but if errors
surface in practice we may need to add an exception for the Throws
matcher.

Closes #1637

Async matchers are implemented as a hack where they usually appear to
succeed synchronously, and may report an error for the test case later.
This is incompatible with meta matchers or "operator" matchers `isNot`
and `anyOf` where the synchronous answer is trusted and other values are
derived from it. In the case of `isNot` this causes false positives, and
for `anyOf` it causes false negatives.

Historically it was infeasible to add knowledg about `AsyncMatcher` in
the definition of these operators, because async matching was
implemented in the test runner directly and the matchers exposed by the
test runner are a superset of those defined by the matcher package. Now
that `expect` and the async matchers are definied in `package:matcher`
it is possible to detect when an async matcher is used this way.

There is one async matcher when does often serve as a synchronous
matcher in practice - the `throwsA` matcher is used for both synchronous
and asynchronous errors, the same matcher is uesed for `Function()`,
`Future Function()` and `Future` values. I think the combination of
`throwsA` and `isNot` or `anyOf` is likely to be rare, but if errors
surface in practice we may need to add an exception for the `Throws`
matcher.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

PR Health

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

@natebosch
Copy link
Copy Markdown
Member Author

This is still a tricky import to add internally because AsyncMatcher uses fail from package:test_api/hooks.dart and internally that library is testonly, but there are some non-testonly uses of matcher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

isNot matcher is not negating the result of a wraped matcher

1 participant