Allow similar use between sync and async checks - #2683
Conversation
Towards #2639 Originally async expectations which extract a value also returned a `Subject` like synchronous extensions still do, but they were changed to accept `AsyncCondition` callback arguments for ergonomics. Add support for synchronous `Condition` callbacks on a few expectations that return subjects for authors who prefer to use the async style for synchronous checks too. Affected APIs: - `Subject.isA` - `Subject.isNotNull` - `Subject<T Function()>.throws` - `Subject<T Function()>.returnsNormally` Restore the `Future<Subject>` return value from async check extensions that accept an optional `AsyncCondition` argument. This allows usage of other patterns which some authors may feel are better aligned with the sync checks of the original pattern. Affected APIs: - `Subject<Future>.completes` - `Subject<Future>.throws` - `Subject<StreamQueue>.emits` - `Subject<StreamQueue>.emitsError` Refactor tests into finer grained cases and add cases for the new arguments.
PR HealthChangelog Entry ✔️
Changes to files need to be accounted for in their respective changelogs. This check can be disabled by tagging the PR with |
jonasfj
left a comment
There was a problem hiding this comment.
Maybe, write some examples with old and new things, it a bit hard to follow the explanations.
From what I understand, and this is just my personal feelings.
I would probably rather avoid (await check(...), but I also see little harm in allowing it.
I would probably also prefer check(...).isA<T>().something(..) over check(...).isA<T>((it) => it.something(..)).
But I can see no harm in allowing both things. And it kind of makes both sync and async APIs behave similar, even if the async API still requires an await inside the expression.
There is obviously no way we are going to do an extension on Future<Subject<int>> that will allow people to do:
await check(Future.value(42)).lessThan(100);That would be cool, but subclassing Future is probably a crime. and making a global expando mapping from Future<Subject> to SomeKindOfHack, is probably borderline insane 🤣🤣🤣
So even if it could be done, it probably shouldn't be done 🤣
-- But mentally that's what I would prefer to write, but if I had to reason about how it works, I probably wouldn't prefer it.
| (await check(_futureFail()).throws<UnimplementedError>()) | ||
| .has((p0) => p0.message, 'message') | ||
| .isNull(); |
There was a problem hiding this comment.
These are just "feeling", feel free to ignore.
I suspect I would rarely use (await check(...)).whatever(), because inline await is just ugly when it has to be wrapped that way.
I suppose I might find myself occasionally wanting to do:
final it = await check(...);
it.has(...);I could imagine that being attractive in some complicated scenarios.
There was a problem hiding this comment.
I suspect I would rarely use
(await check(...)).whatever(), because inlineawaitis just ugly when it has to be wrapped that way.
Yes, this is why I moved await from this pattern and didn't keep it as an option. We've since had at least a few requests to include it. I suspect it will bear out that it won't get used much in practice, but I also think that the corresponding synchronous API that will get used drastically reduces the cost of having 2 ways to do this.
| to the extracted value. | ||
| - Updated `Subject.completes`, `Subject.throws` (async), `StreamChecks.emits`, | ||
| and `StreamChecks.emitsError` to return `Future<Subject>` in addition to | ||
| accepting an optional `AsyncCondition` callback. |
There was a problem hiding this comment.
Consider showing examples like:
- Updated `Subject.isA` to allow `check(42).isA<int>((it) => it.lessThan(100));`| }); | ||
| test('evaluates condition', () { | ||
| check(() => 1).isRejectedBy( | ||
| (it) => it.returnsNormally((it) => it.equals(2)), |
There was a problem hiding this comment.
This is not the new Condition syntax. I'll land that after this one.
# Conflicts: # pkgs/checks/lib/src/checks.dart # pkgs/checks/lib/src/extensions/async.dart # pkgs/checks/lib/src/extensions/function.dart # pkgs/checks/test/extensions/function_test.dart
|
After discussions we plan on moving forward with this and #2697 |
Adds `FutureSubjectExtension` on `Future<Subject<T>>` with a `which` method that applies a `Condition<T>` to the subject upon future completion. This mirrors the `which` method from `CoreChecks` on `Subject`. TAG=agy CONV=f78192e2-b9b4-48ef-a615-31061b25ec89
brianquinlan
left a comment
There was a problem hiding this comment.
I mainly looked at the tests, which seem reasonable.
Remove some unnecessary changes to minimize diff
Closes #2695
Originally async expectations which extract a value also returned a
Subjectlike synchronous extensions still do, but they were changed toaccept
AsyncConditioncallback arguments for ergonomics. Add supportfor synchronous
Conditioncallbacks on a few expectations that returnsubjects for authors who prefer to use the async style for synchronous
checks too.
Affected APIs:
Subject.isASubject.isNotNullSubject<T Function()>.throwsSubject<T Function()>.returnsNormallyRestore the
Future<Subject>return value from async check extensionsthat accept an optional
AsyncConditionargument. This allows usage ofother patterns which some authors may feel are better aligned with the
sync checks of the original pattern.
Affected APIs:
Subject<Future>.completesSubject<Future>.throwsSubject<StreamQueue>.emitsSubject<StreamQueue>.emitsErrorRefactor tests into finer grained cases and add cases for the new
arguments.