Annotate async expectations with awaitNotRequired (#2700) Closes #2139 Asynchronous expectations which return a `Future` already use `TestHandle.current.markPending()` so that test cases are not considered complete until after the expectations are complete. Some uses will want to `await` anyway to maintain execution order in tests, but it's valid to "fire and forget" asynchronous expectations when execution order doesn't matter. Annotate with `@awaitNotRequired` so this is explicit and the `unawaited_futures` lint is suppressed at call sites.
diff --git a/pkgs/checks/CHANGELOG.md b/pkgs/checks/CHANGELOG.md index 513d0f9..adf5b0d 100644 --- a/pkgs/checks/CHANGELOG.md +++ b/pkgs/checks/CHANGELOG.md
@@ -12,6 +12,9 @@ forward exceptions from that call. - Fix a bug when printing a failure message involving a value that returns an empty output from `toString()`. +- Annotated async expectations with `@awaitNotRequired` since tests are safely + held open until expectations complete so it's not always necessary to + explicitly await them. ## 0.3.1
diff --git a/pkgs/checks/lib/src/extensions/async.dart b/pkgs/checks/lib/src/extensions/async.dart index cee3d40..f1fc3f2 100644 --- a/pkgs/checks/lib/src/extensions/async.dart +++ b/pkgs/checks/lib/src/extensions/async.dart
@@ -6,6 +6,7 @@ import 'dart:convert'; import 'package:async/async.dart'; +import 'package:meta/meta.dart' show awaitNotRequired; import '../../context.dart'; @@ -18,6 +19,7 @@ /// /// The returned future will complete when the subject future has completed, /// and [completionCondition] has optionally been checked. + @awaitNotRequired Future<void> completes([AsyncCondition<T>? completionCondition]) { return context.nestAsync<T>(() => ['completes to a value'], (actual) async { try { @@ -79,6 +81,7 @@ /// /// The returned future will complete when the subject future has completed, /// and [errorCondition] has optionally been checked. + @awaitNotRequired Future<void> throws<E extends Object>([AsyncCondition<E>? errorCondition]) { return context.nestAsync<E>( () => ['completes to an error${E == Object ? '' : ' of type $E'}'], @@ -113,6 +116,7 @@ /// Calls [Context.expectAsync] and wraps [predicate] with a transaction. /// /// The transaction is committed if the check passes, or rejected if it fails. + @awaitNotRequired Future<void> _expectAsync( Iterable<String> Function() clause, FutureOr<Rejection?> Function(StreamQueue<T>) predicate, @@ -142,6 +146,7 @@ /// /// The returned future will complete when the stream has emitted, errored, or /// ended, and the [emittedCondition] has optionally been checked. + @awaitNotRequired Future<void> emits([AsyncCondition<T>? emittedCondition]) { return context.nestAsync<T>(() => ['emits a value'], (actual) async { if (!await actual.hasNext) { @@ -180,6 +185,7 @@ /// /// The returned future will complete when the stream has emitted, errored, or /// ended, and the [errorCondition] has optionally been checked. + @awaitNotRequired Future<void> emitsError<E extends Object>([ AsyncCondition<E>? errorCondition, ]) { @@ -228,6 +234,7 @@ /// state. /// If this expectation succeeds, consumes the matching event and all prior /// events. + @awaitNotRequired Future<void> emitsThrough(AsyncCondition<T> condition) async { await _expectAsync( () => [ @@ -267,6 +274,7 @@ /// state. /// If this expectation succeeds, consumes as many events from the source /// stream as are consumed by all the conditions. + @awaitNotRequired Future<void> inOrder( Iterable<AsyncCondition<StreamQueue<T>>> conditions, ) async { @@ -325,6 +333,7 @@ /// If this expectation succeeds, consumes the same events from the source /// queue as the satisfied condition. If multiple conditions are satisfied, /// chooses the condition which consumed the most events. + @awaitNotRequired Future<void> anyOf(Iterable<AsyncCondition<StreamQueue<T>>> conditions) { conditions = conditions.toList(); if (conditions.isEmpty) { @@ -412,6 +421,7 @@ /// state. /// If this expectation succeeds, consumes all the events that did not satisfy /// [condition] until the end of the stream. + @awaitNotRequired Future<void> neverEmits(AsyncCondition<T> condition) async { await _expectAsync( () => ['never emits a value that:', ...describe(condition)], @@ -440,6 +450,7 @@ /// /// If a non-matching event is emitted, no events are consumed. /// If a matching event is emitted, that event is consumed. + @awaitNotRequired Future<void> mayEmit(AsyncCondition<T> condition) { return context.expectAsync( () { @@ -472,6 +483,7 @@ /// - A non-matching event is emitted. /// - An error is emitted. /// - The stream closes. + @awaitNotRequired Future<void> mayEmitMultiple(AsyncCondition<T> condition) { return context.expectAsync( () { @@ -503,6 +515,7 @@ /// /// If this expectation fails, the source queue will be left in its original /// state, the event or error that caused it to fail will not be consumed. + @awaitNotRequired Future<void> isDone() async { await _expectAsync(() => ['is done'], (actual) async { if (!await actual.hasNext) return null;