diff --git a/pkgs/checks/lib/src/checks.dart b/pkgs/checks/lib/src/checks.dart
index e3f195eda..bc3a0f44b 100644
--- a/pkgs/checks/lib/src/checks.dart
+++ b/pkgs/checks/lib/src/checks.dart
@@ -65,12 +65,12 @@ Subject<T> check<T>(T value, {String? because}) => Subject._(_TestContext._root(
       // TODO - switch between "a" and "an"
       label: () => ['a $T'],
       fail: (f) {
-        final which = f.rejection.which;
+        final which = f.rejection.which?.call();
         throw TestFailure([
           ...prefixFirst('Expected: ', f.detail.expected),
           ...prefixFirst('Actual: ', f.detail.actual),
           ...indent(
-              prefixFirst('Actual: ', f.rejection.actual), f.detail.depth),
+              prefixFirst('Actual: ', f.rejection.actual()), f.detail.depth),
           if (which != null && which.isNotEmpty)
             ...indent(prefixFirst('Which: ', which), f.detail.depth),
           if (because != null) 'Reason: $because',
@@ -282,6 +282,8 @@ abstract class Context<T> {
       FutureOr<Extracted<R>> Function(T) extract);
 }
 
+Iterable<String> _empty() => const [];
+
 /// A property extracted from a value being checked, or a rejection.
 class Extracted<T> {
   final Rejection? rejection;
@@ -293,7 +295,8 @@ class Extracted<T> {
   /// When a nesting is rejected with an omitted or empty [actual] argument, it
   /// will be filled in with the [literal] representation of the value.
   Extracted.rejection(
-      {Iterable<String> actual = const [], Iterable<String>? which})
+      {Iterable<String> Function() actual = _empty,
+      Iterable<String> Function()? which})
       : rejection = Rejection(actual: actual, which: which),
         value = null;
   Extracted.value(T this.value) : rejection = null;
@@ -306,10 +309,11 @@ class Extracted<T> {
     return Extracted.value(transform(value as T));
   }
 
-  Extracted<T> _fillActual(Object? actual) => rejection == null ||
-          rejection!.actual.isNotEmpty
-      ? this
-      : Extracted.rejection(actual: literal(actual), which: rejection!.which);
+  Extracted<T> _fillActual(Object? actual) =>
+      rejection == null || rejection!.actual != _empty
+          ? this
+          : Extracted.rejection(
+              actual: () => literal(actual), which: rejection!.which);
 }
 
 abstract class _Optional<T> {
@@ -682,7 +686,7 @@ class Rejection {
   /// message. All lines in the message will be indented to the level of the
   /// expectation in the description, and printed following the descriptions of
   /// any expectations that have already passed.
-  final Iterable<String> actual;
+  final Iterable<String> Function() actual;
 
   /// A description of the way that [actual] failed to meet the expectation.
   ///
@@ -696,13 +700,13 @@ class Rejection {
   ///
   /// When provided, this is printed following a "Which: " label at the end of
   /// the output for the failure message.
-  final Iterable<String>? which;
+  final Iterable<String> Function()? which;
 
-  Rejection _fillActual(Object? value) => actual.isNotEmpty
+  Rejection _fillActual(Object? value) => actual != _empty
       ? this
-      : Rejection(actual: literal(value), which: which);
+      : Rejection(actual: () => literal(value), which: which);
 
-  Rejection({this.actual = const [], this.which});
+  Rejection({this.actual = _empty, this.which});
 }
 
 class ConditionSubject<T> implements Subject<T>, Condition<T> {
diff --git a/pkgs/checks/lib/src/collection_equality.dart b/pkgs/checks/lib/src/collection_equality.dart
index bd1194727..88aa9a6c0 100644
--- a/pkgs/checks/lib/src/collection_equality.dart
+++ b/pkgs/checks/lib/src/collection_equality.dart
@@ -26,11 +26,12 @@ import 'package:checks/context.dart';
 /// Collections may be nested to a maximum depth of 1000. Recursive collections
 /// are not allowed.
 /// {@endtemplate}
-Iterable<String>? deepCollectionEquals(Object actual, Object expected) {
+Iterable<String> Function()? deepCollectionEquals(
+    Object actual, Object expected) {
   try {
     return _deepCollectionEquals(actual, expected, 0);
   } on _ExceededDepthError {
-    return ['exceeds the depth limit of $_maxDepth'];
+    return () => ['exceeds the depth limit of $_maxDepth'];
   }
 }
 
@@ -38,7 +39,7 @@ const _maxDepth = 1000;
 
 class _ExceededDepthError extends Error {}
 
-Iterable<String>? _deepCollectionEquals(
+Iterable<String> Function()? _deepCollectionEquals(
     Object actual, Object expected, int depth) {
   assert(actual is Iterable || actual is Map);
   assert(expected is Iterable || expected is Map);
@@ -50,7 +51,7 @@ Iterable<String>? _deepCollectionEquals(
     final currentExpected = toCheck.expected;
     final path = toCheck.path;
     final currentDepth = toCheck.depth;
-    Iterable<String>? rejectionWhich;
+    Iterable<String> Function()? rejectionWhich;
     if (currentExpected is Set) {
       rejectionWhich = _findSetDifference(
           currentActual, currentExpected, path, currentDepth);
@@ -67,10 +68,10 @@ Iterable<String>? _deepCollectionEquals(
   return null;
 }
 
-List<String>? _findIterableDifference(Object? actual,
+List<String> Function()? _findIterableDifference(Object? actual,
     Iterable<Object?> expected, _Path path, Queue<_Search> queue, int depth) {
   if (actual is! Iterable) {
-    return ['${path}is not an Iterable'];
+    return () => ['${path}is not an Iterable'];
   }
   var actualIterator = actual.iterator;
   var expectedIterator = expected.iterator;
@@ -79,16 +80,16 @@ List<String>? _findIterableDifference(Object? actual,
     var expectedNext = expectedIterator.moveNext();
     if (!expectedNext && !actualNext) break;
     if (!expectedNext) {
-      return [
-        '${path}has more elements than expected',
-        'expected an iterable with $index element(s)'
-      ];
+      return () => [
+            '${path}has more elements than expected',
+            'expected an iterable with $index element(s)'
+          ];
     }
     if (!actualNext) {
-      return [
-        '${path}has too few elements',
-        'expected an iterable with at least ${index + 1} element(s)'
-      ];
+      return () => [
+            '${path}has too few elements',
+            'expected an iterable with at least ${index + 1} element(s)'
+          ];
     }
     var actualValue = actualIterator.current;
     var expectedValue = expectedIterator.current;
@@ -99,22 +100,23 @@ List<String>? _findIterableDifference(Object? actual,
     } else if (expectedValue is Condition) {
       final failure = softCheck(actualValue, expectedValue);
       if (failure != null) {
-        final which = failure.rejection.which;
-        return [
-          'has an element ${path.append(index)}that:',
-          ...indent(failure.detail.actual.skip(1)),
-          ...indent(prefixFirst('Actual: ', failure.rejection.actual),
-              failure.detail.depth + 1),
-          if (which != null)
-            ...indent(prefixFirst('which ', which), failure.detail.depth + 1)
-        ];
+        final which = failure.rejection.which?.call();
+        return () => [
+              'has an element ${path.append(index)}that:',
+              ...indent(failure.detail.actual.skip(1)),
+              ...indent(prefixFirst('Actual: ', failure.rejection.actual()),
+                  failure.detail.depth + 1),
+              if (which != null)
+                ...indent(
+                    prefixFirst('which ', which), failure.detail.depth + 1)
+            ];
       }
     } else {
       if (actualValue != expectedValue) {
-        return [
-          ...prefixFirst('${path.append(index)}is ', literal(actualValue)),
-          ...prefixFirst('which does not equal ', literal(expectedValue))
-        ];
+        return () => [
+              ...prefixFirst('${path.append(index)}is ', literal(actualValue)),
+              ...prefixFirst('which does not equal ', literal(expectedValue))
+            ];
       }
     }
   }
@@ -134,10 +136,10 @@ bool _elementMatches(Object? actual, Object? expected, int depth) {
   return expected == actual;
 }
 
-Iterable<String>? _findSetDifference(
+Iterable<String> Function()? _findSetDifference(
     Object? actual, Set<Object?> expected, _Path path, int depth) {
   if (actual is! Set) {
-    return ['${path}is not a Set'];
+    return () => ['${path}is not a Set'];
   }
   return unorderedCompare(
     actual,
@@ -154,10 +156,10 @@ Iterable<String>? _findSetDifference(
   );
 }
 
-Iterable<String>? _findMapDifference(
+Iterable<String> Function()? _findMapDifference(
     Object? actual, Map<Object?, Object?> expected, _Path path, int depth) {
   if (actual is! Map) {
-    return ['${path}is not a Map'];
+    return () => ['${path}is not a Map'];
   }
   Iterable<String> describeEntry(MapEntry<Object?, Object?> entry) {
     final key = literal(entry.key);
@@ -241,7 +243,7 @@ class _Search {
 /// Runtime is at least `O(|actual||expected|)`, and for collections with many
 /// elements which compare as equal the runtime can reach
 /// `O((|actual| + |expected|)^2.5)`.
-Iterable<String>? unorderedCompare<T, E>(
+Iterable<String> Function()? unorderedCompare<T, E>(
     Iterable<T> actual,
     Iterable<E> expected,
     bool Function(T, E) elementsEqual,
@@ -261,12 +263,12 @@ Iterable<String>? unorderedCompare<T, E>(
   final unpaired = _findUnpaired(adjacency, indexedActual.length);
   if (unpaired.first.isNotEmpty) {
     final firstUnmatched = indexedExpected[unpaired.first.first];
-    return unmatchedExpected(
+    return () => unmatchedExpected(
         firstUnmatched, unpaired.first.first, unpaired.first.length);
   }
   if (unpaired.last.isNotEmpty) {
     final firstUnmatched = indexedActual[unpaired.last.first];
-    return unmatchedActual(
+    return () => unmatchedActual(
         firstUnmatched, unpaired.last.first, unpaired.last.length);
   }
   return null;
diff --git a/pkgs/checks/lib/src/extensions/async.dart b/pkgs/checks/lib/src/extensions/async.dart
index bde4ba0fb..66c6f166b 100644
--- a/pkgs/checks/lib/src/extensions/async.dart
+++ b/pkgs/checks/lib/src/extensions/async.dart
@@ -20,12 +20,12 @@ extension FutureChecks<T> on Subject<Future<T>> {
         try {
           return Extracted.value(await actual);
         } catch (e, st) {
-          return Extracted.rejection(actual: [
-            'a future that completes as an error'
-          ], which: [
-            ...prefixFirst('threw ', postfixLast(' at:', literal(e))),
-            ...(const LineSplitter()).convert(st.toString())
-          ]);
+          return Extracted.rejection(
+              actual: () => ['a future that completes as an error'],
+              which: () => [
+                    ...prefixFirst('threw ', postfixLast(' at:', literal(e))),
+                    ...(const LineSplitter()).convert(st.toString())
+                  ]);
         }
       });
 
@@ -42,14 +42,15 @@ extension FutureChecks<T> on Subject<Future<T>> {
     context.expectUnawaited(() => ['does not complete'], (actual, reject) {
       unawaited(actual.then((r) {
         reject(Rejection(
-            actual: prefixFirst('a future that completed to ', literal(r))));
+            actual: () =>
+                prefixFirst('a future that completed to ', literal(r))));
       }, onError: (e, st) {
-        reject(Rejection(actual: [
-          'a future that completed as an error:'
-        ], which: [
-          ...prefixFirst('threw ', literal(e)),
-          ...(const LineSplitter()).convert(st.toString())
-        ]));
+        reject(Rejection(
+            actual: () => ['a future that completed as an error:'],
+            which: () => [
+                  ...prefixFirst('threw ', literal(e)),
+                  ...(const LineSplitter()).convert(st.toString())
+                ]));
       }));
     });
   }
@@ -64,18 +65,19 @@ extension FutureChecks<T> on Subject<Future<T>> {
           () => ['completes to an error${E == Object ? '' : ' of type $E'}'],
           (actual) async {
         try {
+          final result = await actual;
           return Extracted.rejection(
-              actual: prefixFirst('completed to ', literal(await actual)),
-              which: ['did not throw']);
+              actual: () => prefixFirst('completed to ', literal(result)),
+              which: () => ['did not throw']);
         } on E catch (e) {
           return Extracted.value(e);
         } catch (e, st) {
           return Extracted.rejection(
-              actual: prefixFirst('completed to error ', literal(e)),
-              which: [
-                'threw an exception that is not a $E at:',
-                ...(const LineSplitter()).convert(st.toString())
-              ]);
+              actual: () => prefixFirst('completed to error ', literal(e)),
+              which: () => [
+                    'threw an exception that is not a $E at:',
+                    ...(const LineSplitter()).convert(st.toString())
+                  ]);
         }
       });
 }
@@ -113,19 +115,19 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
       context.nestAsync<T>(() => ['emits a value'], (actual) async {
         if (!await actual.hasNext) {
           return Extracted.rejection(
-              actual: ['a stream'],
-              which: ['closed without emitting enough values']);
+              actual: () => ['a stream'],
+              which: () => ['closed without emitting enough values']);
         }
         try {
           await actual.peek;
           return Extracted.value(await actual.next);
         } catch (e, st) {
           return Extracted.rejection(
-              actual: prefixFirst('a stream with error ', literal(e)),
-              which: [
-                'emitted an error instead of a value at:',
-                ...(const LineSplitter()).convert(st.toString())
-              ]);
+              actual: () => prefixFirst('a stream with error ', literal(e)),
+              which: () => [
+                    'emitted an error instead of a value at:',
+                    ...(const LineSplitter()).convert(st.toString())
+                  ]);
         }
       });
 
@@ -145,24 +147,25 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
           (actual) async {
         if (!await actual.hasNext) {
           return Extracted.rejection(
-              actual: ['a stream'],
-              which: ['closed without emitting an expected error']);
+              actual: () => ['a stream'],
+              which: () => ['closed without emitting an expected error']);
         }
         try {
           final value = await actual.peek;
           return Extracted.rejection(
-              actual: prefixFirst('a stream emitting value ', literal(value)),
-              which: ['closed without emitting an error']);
+              actual: () =>
+                  prefixFirst('a stream emitting value ', literal(value)),
+              which: () => ['closed without emitting an error']);
         } on E catch (e) {
           await actual.next.then<void>((_) {}, onError: (_) {});
           return Extracted.value(e);
         } catch (e, st) {
           return Extracted.rejection(
-              actual: prefixFirst('a stream with error ', literal(e)),
-              which: [
-                'emitted an error which is not $E at:',
-                ...(const LineSplitter()).convert(st.toString())
-              ]);
+              actual: () => prefixFirst('a stream with error ', literal(e)),
+              which: () => [
+                    'emitted an error which is not $E at:',
+                    ...(const LineSplitter()).convert(st.toString())
+                  ]);
         }
       });
 
@@ -193,8 +196,9 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
         count++;
       }
       return Rejection(
-          actual: ['a stream'],
-          which: ['ended after emitting $count elements with none matching']);
+          actual: () => ['a stream'],
+          which: () =>
+              ['ended after emitting $count elements with none matching']);
     });
   }
 
@@ -227,24 +231,27 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
         descriptions.addAll(await describeAsync(condition));
         final failure = await softCheckAsync(actual, condition);
         if (failure != null) {
-          final which = failure.rejection.which;
-          return Rejection(actual: [
-            'a stream'
-          ], which: [
-            if (satisfiedCount > 0) 'satisfied $satisfiedCount conditions then',
-            'failed to satisfy the condition at index $satisfiedCount',
-            if (failure.detail.depth > 0) ...[
-              'because it:',
-              ...indent(
-                  failure.detail.actual.skip(1), failure.detail.depth - 1),
-              ...indent(prefixFirst('Actual: ', failure.rejection.actual),
-                  failure.detail.depth),
-              if (which != null)
-                ...indent(prefixFirst('Which: ', which), failure.detail.depth),
-            ] else ...[
-              if (which != null) ...prefixFirst('because it ', which),
-            ],
-          ]);
+          final which = failure.rejection.which?.call();
+          return Rejection(
+              actual: () => ['a stream'],
+              which: () => [
+                    if (satisfiedCount > 0)
+                      'satisfied $satisfiedCount conditions then',
+                    'failed to satisfy the condition at index $satisfiedCount',
+                    if (failure.detail.depth > 0) ...[
+                      'because it:',
+                      ...indent(failure.detail.actual.skip(1),
+                          failure.detail.depth - 1),
+                      ...indent(
+                          prefixFirst('Actual: ', failure.rejection.actual()),
+                          failure.detail.depth),
+                      if (which != null)
+                        ...indent(prefixFirst('Which: ', which),
+                            failure.detail.depth),
+                    ] else ...[
+                      if (which != null) ...prefixFirst('because it ', which),
+                    ],
+                  ]);
         }
         satisfiedCount++;
       }
@@ -296,11 +303,11 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
       }
       transaction.reject();
       Iterable<String> failureDetails(int index, CheckFailure? failure) {
-        final actual = failure!.rejection.actual;
-        final which = failure.rejection.which;
-        final detail = failure.detail;
+        final detail = failure!.detail;
         final failed = 'failed the condition at index $index';
+        final which = failure.rejection.which?.call();
         if (detail.depth > 0) {
+          final actual = failure.rejection.actual();
           return [
             '$failed because it:',
             ...indent(detail.actual.skip(1), detail.depth - 1),
@@ -320,13 +327,13 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
         }
       }
 
-      return Rejection(actual: [
-        'a stream'
-      ], which: [
-        'failed to satisfy any condition',
-        for (var i = 0; i < failures.length; i++)
-          ...failureDetails(i, failures[i]),
-      ]);
+      return Rejection(
+          actual: () => ['a stream'],
+          which: () => [
+                'failed to satisfy any condition',
+                for (var i = 0; i < failures.length; i++)
+                  ...failureDetails(i, failures[i]),
+              ]);
     });
   }
 
@@ -348,12 +355,12 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
       var count = 0;
       await for (var emitted in actual.rest) {
         if (softCheck(emitted, condition) == null) {
-          return Rejection(actual: [
-            'a stream'
-          ], which: [
-            ...prefixFirst('emitted ', literal(emitted)),
-            if (count > 0) 'following $count other items'
-          ]);
+          return Rejection(
+              actual: () => ['a stream'],
+              which: () => [
+                    ...prefixFirst('emitted ', literal(emitted)),
+                    if (count > 0) 'following $count other items'
+                  ]);
         }
         count++;
       }
@@ -420,17 +427,18 @@ extension StreamChecks<T> on Subject<StreamQueue<T>> {
     await _expectAsync(() => ['is done'], (actual) async {
       if (!await actual.hasNext) return null;
       try {
+        final next = await actual.next;
         return Rejection(
-            actual: ['a stream'],
-            which: prefixFirst(
-                'emitted an unexpected value: ', literal(await actual.next)));
+            actual: () => ['a stream'],
+            which: () =>
+                prefixFirst('emitted an unexpected value: ', literal(next)));
       } catch (e, st) {
-        return Rejection(actual: [
-          'a stream'
-        ], which: [
-          ...prefixFirst('emitted an unexpected error: ', literal(e)),
-          ...(const LineSplitter()).convert(st.toString())
-        ]);
+        return Rejection(
+            actual: () => ['a stream'],
+            which: () => [
+                  ...prefixFirst('emitted an unexpected error: ', literal(e)),
+                  ...(const LineSplitter()).convert(st.toString())
+                ]);
       }
     });
   }
diff --git a/pkgs/checks/lib/src/extensions/core.dart b/pkgs/checks/lib/src/extensions/core.dart
index 38ba4c76a..be0c9935a 100644
--- a/pkgs/checks/lib/src/extensions/core.dart
+++ b/pkgs/checks/lib/src/extensions/core.dart
@@ -15,7 +15,7 @@ extension CoreChecks<T> on Subject<T> {
         return Extracted.value(extract(value));
       } catch (_) {
         return Extracted.rejection(
-            which: ['threw while trying to read property']);
+            which: () => ['threw while trying to read property']);
       }
     });
   }
@@ -45,7 +45,7 @@ extension CoreChecks<T> on Subject<T> {
       (actual) {
         if (softCheck(actual, condition) != null) return null;
         return Rejection(
-          which: ['is a value that: ', ...indent(describe(condition))],
+          which: () => ['is a value that: ', ...indent(describe(condition))],
         );
       },
     );
@@ -62,7 +62,7 @@ extension CoreChecks<T> on Subject<T> {
       for (final condition in conditions) {
         if (softCheck(actual, condition) == null) return null;
       }
-      return Rejection(which: ['did not match any condition']);
+      return Rejection(which: () => ['did not match any condition']);
     });
   }
 
@@ -72,7 +72,7 @@ extension CoreChecks<T> on Subject<T> {
   Subject<R> isA<R>() {
     return context.nest<R>(() => ['is a $R'], (actual) {
       if (actual is! R) {
-        return Extracted.rejection(which: ['Is a ${actual.runtimeType}']);
+        return Extracted.rejection(which: () => ['Is a ${actual.runtimeType}']);
       }
       return Extracted.value(actual);
     }, atSameLevel: true);
@@ -82,7 +82,7 @@ extension CoreChecks<T> on Subject<T> {
   void equals(T other) {
     context.expect(() => prefixFirst('equals ', literal(other)), (actual) {
       if (actual == other) return null;
-      return Rejection(which: ['are not equal']);
+      return Rejection(which: () => ['are not equal']);
     });
   }
 
@@ -91,7 +91,7 @@ extension CoreChecks<T> on Subject<T> {
     context.expect(() => prefixFirst('is identical to ', literal(other)),
         (actual) {
       if (identical(actual, other)) return null;
-      return Rejection(which: ['is not identical']);
+      return Rejection(which: () => ['is not identical']);
     });
   }
 }
diff --git a/pkgs/checks/lib/src/extensions/function.dart b/pkgs/checks/lib/src/extensions/function.dart
index 1caa2aeb4..2bbd16d1f 100644
--- a/pkgs/checks/lib/src/extensions/function.dart
+++ b/pkgs/checks/lib/src/extensions/function.dart
@@ -21,14 +21,16 @@ extension ThrowsCheck<T> on Subject<T Function()> {
       try {
         final result = actual();
         return Extracted.rejection(
-          actual: prefixFirst('a function that returned ', literal(result)),
-          which: ['did not throw'],
+          actual: () =>
+              prefixFirst('a function that returned ', literal(result)),
+          which: () => ['did not throw'],
         );
       } catch (e) {
         if (e is E) return Extracted.value(e as E);
         return Extracted.rejection(
-            actual: prefixFirst('a function that threw error ', literal(e)),
-            which: ['did not throw an $E']);
+            actual: () =>
+                prefixFirst('a function that threw error ', literal(e)),
+            which: () => ['did not throw an $E']);
       }
     });
   }
@@ -44,12 +46,12 @@ extension ThrowsCheck<T> on Subject<T Function()> {
       try {
         return Extracted.value(actual());
       } catch (e, st) {
-        return Extracted.rejection(actual: [
-          'a function that throws'
-        ], which: [
-          ...prefixFirst('threw ', literal(e)),
-          ...st.toString().split('\n')
-        ]);
+        return Extracted.rejection(
+            actual: () => ['a function that throws'],
+            which: () => [
+                  ...prefixFirst('threw ', literal(e)),
+                  ...st.toString().split('\n')
+                ]);
       }
     });
   }
diff --git a/pkgs/checks/lib/src/extensions/iterable.dart b/pkgs/checks/lib/src/extensions/iterable.dart
index a48ecc321..5cf65bac1 100644
--- a/pkgs/checks/lib/src/extensions/iterable.dart
+++ b/pkgs/checks/lib/src/extensions/iterable.dart
@@ -16,14 +16,14 @@ extension IterableChecks<T> on Subject<Iterable<T>> {
   void isEmpty() {
     context.expect(() => const ['is empty'], (actual) {
       if (actual.isEmpty) return null;
-      return Rejection(which: ['is not empty']);
+      return Rejection(which: () => ['is not empty']);
     });
   }
 
   void isNotEmpty() {
     context.expect(() => const ['is not empty'], (actual) {
       if (actual.isNotEmpty) return null;
-      return Rejection(which: ['is not empty']);
+      return Rejection(which: () => ['is not empty']);
     });
   }
 
@@ -33,10 +33,10 @@ extension IterableChecks<T> on Subject<Iterable<T>> {
     context.expect(() {
       return prefixFirst('contains ', literal(element));
     }, (actual) {
-      if (actual.isEmpty) return Rejection(actual: ['an empty iterable']);
+      if (actual.isEmpty) return Rejection(actual: () => ['an empty iterable']);
       if (actual.contains(element)) return null;
       return Rejection(
-          which: prefixFirst('does not contain ', literal(element)));
+          which: () => prefixFirst('does not contain ', literal(element)));
     });
   }
 
@@ -77,12 +77,13 @@ extension IterableChecks<T> on Subject<Iterable<T>> {
                 : currentExpected == element;
         if (matches && ++expectedIndex >= expected.length) return null;
       }
-      return Rejection(which: [
-        ...prefixFirst(
-            'did not have an element matching the expectation at index '
-            '$expectedIndex ',
-            literal(expected[expectedIndex])),
-      ]);
+      return Rejection(
+          which: () => [
+                ...prefixFirst(
+                    'did not have an element matching the expectation at index '
+                    '$expectedIndex ',
+                    literal(expected[expectedIndex])),
+              ]);
     });
   }
 
@@ -97,11 +98,11 @@ extension IterableChecks<T> on Subject<Iterable<T>> {
         ...conditionDescription,
       ];
     }, (actual) {
-      if (actual.isEmpty) return Rejection(actual: ['an empty iterable']);
+      if (actual.isEmpty) return Rejection(actual: () => ['an empty iterable']);
       for (var e in actual) {
         if (softCheck(e, elementCondition) == null) return null;
       }
-      return Rejection(which: ['Contains no matching element']);
+      return Rejection(which: () => ['Contains no matching element']);
     });
   }
 
@@ -123,15 +124,17 @@ extension IterableChecks<T> on Subject<Iterable<T>> {
         final element = iterator.current;
         final failure = softCheck(element, elementCondition);
         if (failure == null) continue;
-        final which = failure.rejection.which;
-        return Rejection(which: [
-          'has an element at index $i that:',
-          ...indent(failure.detail.actual.skip(1)),
-          ...indent(prefixFirst('Actual: ', failure.rejection.actual),
-              failure.detail.depth + 1),
-          if (which != null && which.isNotEmpty)
-            ...indent(prefixFirst('Which: ', which), failure.detail.depth + 1),
-        ]);
+        final which = failure.rejection.which?.call();
+        return Rejection(
+            which: () => [
+                  'has an element at index $i that:',
+                  ...indent(failure.detail.actual.skip(1)),
+                  ...indent(prefixFirst('Actual: ', failure.rejection.actual()),
+                      failure.detail.depth + 1),
+                  if (which != null && which.isNotEmpty)
+                    ...indent(prefixFirst('Which: ', which),
+                        failure.detail.depth + 1),
+                ]);
       }
       return null;
     });
@@ -230,27 +233,30 @@ extension IterableChecks<T> on Subject<Iterable<T>> {
       for (var i = 0; i < expected.length; i++) {
         final expectedValue = expected[i];
         if (!iterator.moveNext()) {
-          return Rejection(which: [
-            'has too few elements, there is no element to match at index $i'
-          ]);
+          return Rejection(
+              which: () => [
+                    'has too few elements, '
+                        'there is no element to match at index $i'
+                  ]);
         }
         final actualValue = iterator.current;
         final failure = softCheck(actualValue, elementCondition(expectedValue));
         if (failure == null) continue;
         final innerDescription = describe<T>(elementCondition(expectedValue));
-        final which = failure.rejection.which;
-        return Rejection(which: [
-          'does not have an element at index $i that:',
-          ...innerDescription,
-          ...prefixFirst(
-              'Actual element at index $i: ', failure.rejection.actual),
-          if (which != null) ...prefixFirst('Which: ', which),
-        ]);
+        final which = failure.rejection.which?.call();
+        return Rejection(
+            which: () => [
+                  'does not have an element at index $i that:',
+                  ...innerDescription,
+                  ...prefixFirst('Actual element at index $i: ',
+                      failure.rejection.actual()),
+                  if (which != null) ...prefixFirst('Which: ', which),
+                ]);
       }
       if (!iterator.moveNext()) return null;
-      return Rejection(which: [
-        'has too many elements, expected exactly ${expected.length}'
-      ]);
+      return Rejection(
+          which: () =>
+              ['has too many elements, expected exactly ${expected.length}']);
     });
   }
 }
diff --git a/pkgs/checks/lib/src/extensions/map.dart b/pkgs/checks/lib/src/extensions/map.dart
index 4984b5fb2..c3dca990e 100644
--- a/pkgs/checks/lib/src/extensions/map.dart
+++ b/pkgs/checks/lib/src/extensions/map.dart
@@ -18,7 +18,8 @@ extension MapChecks<K, V> on Subject<Map<K, V>> {
         () => prefixFirst('contains a value for ', literal(key)), (actual) {
       if (!actual.containsKey(key)) {
         return Extracted.rejection(
-            which: prefixFirst('does not contain the key ', literal(key)));
+            which: () =>
+                prefixFirst('does not contain the key ', literal(key)));
       }
       return Extracted.value(actual[key] as V);
     });
@@ -27,14 +28,14 @@ extension MapChecks<K, V> on Subject<Map<K, V>> {
   void isEmpty() {
     context.expect(() => const ['is empty'], (actual) {
       if (actual.isEmpty) return null;
-      return Rejection(which: ['is not empty']);
+      return Rejection(which: () => ['is not empty']);
     });
   }
 
   void isNotEmpty() {
     context.expect(() => const ['is not empty'], (actual) {
       if (actual.isNotEmpty) return null;
-      return Rejection(which: ['is not empty']);
+      return Rejection(which: () => ['is not empty']);
     });
   }
 
@@ -43,7 +44,7 @@ extension MapChecks<K, V> on Subject<Map<K, V>> {
     context.expect(() => prefixFirst('contains key ', literal(key)), (actual) {
       if (actual.containsKey(key)) return null;
       return Rejection(
-          which: prefixFirst('does not contain key ', literal(key)));
+          which: () => prefixFirst('does not contain key ', literal(key)));
     });
   }
 
@@ -58,11 +59,11 @@ extension MapChecks<K, V> on Subject<Map<K, V>> {
         ...conditionDescription,
       ];
     }, (actual) {
-      if (actual.isEmpty) return Rejection(actual: ['an empty map']);
+      if (actual.isEmpty) return Rejection(actual: () => ['an empty map']);
       for (var k in actual.keys) {
         if (softCheck(k, keyCondition) == null) return null;
       }
-      return Rejection(which: ['Contains no matching key']);
+      return Rejection(which: () => ['Contains no matching key']);
     });
   }
 
@@ -72,7 +73,7 @@ extension MapChecks<K, V> on Subject<Map<K, V>> {
         (actual) {
       if (actual.containsValue(value)) return null;
       return Rejection(
-          which: prefixFirst('does not contain value ', literal(value)));
+          which: () => prefixFirst('does not contain value ', literal(value)));
     });
   }
 
@@ -87,11 +88,11 @@ extension MapChecks<K, V> on Subject<Map<K, V>> {
         ...conditionDescription,
       ];
     }, (actual) {
-      if (actual.isEmpty) return Rejection(actual: ['an empty map']);
+      if (actual.isEmpty) return Rejection(actual: () => ['an empty map']);
       for (var v in actual.values) {
         if (softCheck(v, valueCondition) == null) return null;
       }
-      return Rejection(which: ['Contains no matching value']);
+      return Rejection(which: () => ['Contains no matching value']);
     });
   }
 
diff --git a/pkgs/checks/lib/src/extensions/math.dart b/pkgs/checks/lib/src/extensions/math.dart
index b087aef2b..3024a3cb8 100644
--- a/pkgs/checks/lib/src/extensions/math.dart
+++ b/pkgs/checks/lib/src/extensions/math.dart
@@ -9,7 +9,7 @@ extension NumChecks on Subject<num> {
   void isGreaterThan(num other) {
     context.expect(() => ['is greater than <$other>'], (actual) {
       if (actual > other) return null;
-      return Rejection(which: ['is not greater than <$other>']);
+      return Rejection(which: () => ['is not greater than <$other>']);
     });
   }
 
@@ -17,7 +17,8 @@ extension NumChecks on Subject<num> {
   void isGreaterOrEqual(num other) {
     context.expect(() => ['is greater than or equal to <$other>'], (actual) {
       if (actual >= other) return null;
-      return Rejection(which: ['is not greater than or equal to <$other>']);
+      return Rejection(
+          which: () => ['is not greater than or equal to <$other>']);
     });
   }
 
@@ -25,7 +26,7 @@ extension NumChecks on Subject<num> {
   void isLessThan(num other) {
     context.expect(() => ['is less than <$other>'], (actual) {
       if (actual < other) return null;
-      return Rejection(which: ['is not less than <$other>']);
+      return Rejection(which: () => ['is not less than <$other>']);
     });
   }
 
@@ -33,7 +34,7 @@ extension NumChecks on Subject<num> {
   void isLessOrEqual(num other) {
     context.expect(() => ['is less than or equal to <$other>'], (actual) {
       if (actual <= other) return null;
-      return Rejection(which: ['is not less than or equal to <$other>']);
+      return Rejection(which: () => ['is not less than or equal to <$other>']);
     });
   }
 
@@ -41,7 +42,7 @@ extension NumChecks on Subject<num> {
   void isNaN() {
     context.expect(() => ['is not a number (NaN)'], (actual) {
       if (actual.isNaN) return null;
-      return Rejection(which: ['is a number']);
+      return Rejection(which: () => ['is a number']);
     });
   }
 
@@ -49,7 +50,7 @@ extension NumChecks on Subject<num> {
   void isNotNaN() {
     context.expect(() => ['is a number (not NaN)'], (actual) {
       if (!actual.isNaN) return null;
-      return Rejection(which: ['is not a number (NaN)']);
+      return Rejection(which: () => ['is not a number (NaN)']);
     });
   }
 
@@ -57,7 +58,7 @@ extension NumChecks on Subject<num> {
   void isNegative() {
     context.expect(() => ['is negative'], (actual) {
       if (actual.isNegative) return null;
-      return Rejection(which: ['is not negative']);
+      return Rejection(which: () => ['is not negative']);
     });
   }
 
@@ -65,7 +66,7 @@ extension NumChecks on Subject<num> {
   void isNotNegative() {
     context.expect(() => ['is not negative'], (actual) {
       if (!actual.isNegative) return null;
-      return Rejection(which: ['is negative']);
+      return Rejection(which: () => ['is negative']);
     });
   }
 
@@ -73,7 +74,7 @@ extension NumChecks on Subject<num> {
   void isFinite() {
     context.expect(() => ['is finite'], (actual) {
       if (actual.isFinite) return null;
-      return Rejection(which: ['is not finite']);
+      return Rejection(which: () => ['is not finite']);
     });
   }
 
@@ -84,7 +85,7 @@ extension NumChecks on Subject<num> {
   void isNotFinite() {
     context.expect(() => ['is not finite'], (actual) {
       if (!actual.isFinite) return null;
-      return Rejection(which: ['is finite']);
+      return Rejection(which: () => ['is finite']);
     });
   }
 
@@ -94,7 +95,7 @@ extension NumChecks on Subject<num> {
   void isInfinite() {
     context.expect(() => ['is infinite'], (actual) {
       if (actual.isInfinite) return null;
-      return Rejection(which: ['is not infinite']);
+      return Rejection(which: () => ['is not infinite']);
     });
   }
 
@@ -104,7 +105,7 @@ extension NumChecks on Subject<num> {
   void isNotInfinite() {
     context.expect(() => ['is not infinite'], (actual) {
       if (!actual.isInfinite) return null;
-      return Rejection(which: ['is infinite']);
+      return Rejection(which: () => ['is infinite']);
     });
   }
 
@@ -114,7 +115,7 @@ extension NumChecks on Subject<num> {
     context.expect(() => ['is within <$delta> of <$other>'], (actual) {
       final difference = (other - actual).abs();
       if (difference <= delta) return null;
-      return Rejection(which: ['differs by <$difference>']);
+      return Rejection(which: () => ['differs by <$difference>']);
     });
   }
 }
diff --git a/pkgs/checks/lib/src/extensions/string.dart b/pkgs/checks/lib/src/extensions/string.dart
index d57e8403b..5f31f8bea 100644
--- a/pkgs/checks/lib/src/extensions/string.dart
+++ b/pkgs/checks/lib/src/extensions/string.dart
@@ -14,7 +14,7 @@ extension StringChecks on Subject<String> {
     context.expect(() => prefixFirst('contains ', literal(pattern)), (actual) {
       if (actual.contains(pattern)) return null;
       return Rejection(
-        which: prefixFirst('Does not contain ', literal(pattern)),
+        which: () => prefixFirst('Does not contain ', literal(pattern)),
       );
     });
   }
@@ -24,14 +24,14 @@ extension StringChecks on Subject<String> {
   void isEmpty() {
     context.expect(() => const ['is empty'], (actual) {
       if (actual.isEmpty) return null;
-      return Rejection(which: ['is not empty']);
+      return Rejection(which: () => ['is not empty']);
     });
   }
 
   void isNotEmpty() {
     context.expect(() => const ['is not empty'], (actual) {
       if (actual.isNotEmpty) return null;
-      return Rejection(which: ['is empty']);
+      return Rejection(which: () => ['is empty']);
     });
   }
 
@@ -41,7 +41,7 @@ extension StringChecks on Subject<String> {
       (actual) {
         if (actual.startsWith(other)) return null;
         return Rejection(
-          which: prefixFirst('does not start with ', literal(other)),
+          which: () => prefixFirst('does not start with ', literal(other)),
         );
       },
     );
@@ -53,7 +53,7 @@ extension StringChecks on Subject<String> {
       (actual) {
         if (actual.endsWith(other)) return null;
         return Rejection(
-          which: prefixFirst('does not end with ', literal(other)),
+          which: () => prefixFirst('does not end with ', literal(other)),
         );
       },
     );
@@ -64,7 +64,7 @@ extension StringChecks on Subject<String> {
     context.expect(() => prefixFirst('matches ', literal(expected)), (actual) {
       if (expected.hasMatch(actual)) return null;
       return Rejection(
-          which: prefixFirst('does not match ', literal(expected)));
+          which: () => prefixFirst('does not match ', literal(expected)));
     });
   }
 
@@ -81,12 +81,13 @@ extension StringChecks on Subject<String> {
       for (var s in expected) {
         var index = actual.indexOf(s, fromIndex);
         if (index < 0) {
-          return Rejection(which: [
-            ...prefixFirst(
-                'does not have a match for the substring ', literal(s)),
-            if (fromIndex != 0)
-              'following the other matches up to character $fromIndex'
-          ]);
+          return Rejection(
+              which: () => [
+                    ...prefixFirst(
+                        'does not have a match for the substring ', literal(s)),
+                    if (fromIndex != 0)
+                      'following the other matches up to character $fromIndex'
+                  ]);
         }
         fromIndex = index + s.length;
       }
@@ -155,36 +156,39 @@ Rejection? _findDifference(String actual, String expected,
   if (i == minLength) {
     if (escapedExpected.length < escapedActual.length) {
       if (expected.isEmpty) {
-        return Rejection(which: ['is not the empty string']);
+        return Rejection(which: () => ['is not the empty string']);
       }
-      return Rejection(which: [
-        'is too long with unexpected trailing characters:',
-        _trailing(escapedActualDisplay, i)
-      ]);
+      return Rejection(
+          which: () => [
+                'is too long with unexpected trailing characters:',
+                _trailing(escapedActualDisplay, i)
+              ]);
     } else {
       if (actual.isEmpty) {
-        return Rejection(actual: [
-          'an empty string'
-        ], which: [
-          'is missing all expected characters:',
-          _trailing(escapedExpectedDisplay, 0)
-        ]);
+        return Rejection(
+            actual: () => ['an empty string'],
+            which: () => [
+                  'is missing all expected characters:',
+                  _trailing(escapedExpectedDisplay, 0)
+                ]);
       }
-      return Rejection(which: [
-        'is too short with missing trailing characters:',
-        _trailing(escapedExpectedDisplay, i)
-      ]);
+      return Rejection(
+          which: () => [
+                'is too short with missing trailing characters:',
+                _trailing(escapedExpectedDisplay, i)
+              ]);
     }
   } else {
     final indentation = ' ' * (i > 10 ? 14 : i);
-    return Rejection(which: [
-      'differs at offset $i:',
-      '${_leading(escapedExpectedDisplay, i)}'
-          '${_trailing(escapedExpectedDisplay, i)}',
-      '${_leading(escapedActualDisplay, i)}'
-          '${_trailing(escapedActualDisplay, i)}',
-      '$indentation^'
-    ]);
+    return Rejection(
+        which: () => [
+              'differs at offset $i:',
+              '${_leading(escapedExpectedDisplay, i)}'
+                  '${_trailing(escapedExpectedDisplay, i)}',
+              '${_leading(escapedActualDisplay, i)}'
+                  '${_trailing(escapedActualDisplay, i)}',
+              '$indentation^'
+            ]);
   }
 }
 
diff --git a/pkgs/checks/test/extensions/collection_equality_test.dart b/pkgs/checks/test/extensions/collection_equality_test.dart
index 64eb5bfb0..5e0c49002 100644
--- a/pkgs/checks/test/extensions/collection_equality_test.dart
+++ b/pkgs/checks/test/extensions/collection_equality_test.dart
@@ -82,18 +82,24 @@ void main() {
         ['a']
       ], [
         {'a'}
-      ])).isNotNull().deepEquals(['at [<0>] is not a Set']);
+      ])).isNotNull().returnsNormally().deepEquals(['at [<0>] is not a Set']);
     });
 
     test('reports long iterables', () {
-      check(deepCollectionEquals([0], [])).isNotNull().deepEquals([
+      check(deepCollectionEquals([0], []))
+          .isNotNull()
+          .returnsNormally()
+          .deepEquals([
         'has more elements than expected',
         'expected an iterable with 0 element(s)'
       ]);
     });
 
     test('reports short iterables', () {
-      check(deepCollectionEquals([], [0])).isNotNull().deepEquals([
+      check(deepCollectionEquals([], [0]))
+          .isNotNull()
+          .returnsNormally()
+          .deepEquals([
         'has too few elements',
         'expected an iterable with at least 1 element(s)'
       ]);
@@ -102,12 +108,14 @@ void main() {
     test('reports unequal elements in iterables', () {
       check(deepCollectionEquals([0], [1]))
           .isNotNull()
+          .returnsNormally()
           .deepEquals(['at [<0>] is <0>', 'which does not equal <1>']);
     });
 
     test('reports unmet conditions in iterables', () {
       check(deepCollectionEquals([0], [it()..isA<int>().isGreaterThan(0)]))
           .isNotNull()
+          .returnsNormally()
           .deepEquals([
         'has an element at [<0>] that:',
         '  Actual: <0>',
@@ -119,6 +127,7 @@ void main() {
       check(deepCollectionEquals(
               {'a': 'b'}, {'a': it()..isA<String>().startsWith('a')}))
           .isNotNull()
+          .returnsNormally()
           .deepEquals([
         "has no entry to match 'a': <A value that:",
         '  is a String',
@@ -130,6 +139,7 @@ void main() {
       check(deepCollectionEquals(
               {'b': 'a'}, {it()..isA<String>().startsWith('a'): 'a'}))
           .isNotNull()
+          .returnsNormally()
           .deepEquals([
         'has no entry to match <A value that:',
         '  is a String',
@@ -142,6 +152,7 @@ void main() {
       l.add(l);
       check(deepCollectionEquals(l, l))
           .isNotNull()
+          .returnsNormally()
           .deepEquals(['exceeds the depth limit of 1000']);
     });
 
@@ -150,6 +161,7 @@ void main() {
       s.add(s);
       check(deepCollectionEquals(s, s))
           .isNotNull()
+          .returnsNormally()
           .deepEquals(['exceeds the depth limit of 1000']);
     });
 
@@ -158,6 +170,7 @@ void main() {
       m[m] = 0;
       check(deepCollectionEquals(m, m))
           .isNotNull()
+          .returnsNormally()
           .deepEquals(['exceeds the depth limit of 1000']);
     });
 
@@ -166,6 +179,7 @@ void main() {
       m[0] = m;
       check(deepCollectionEquals(m, m))
           .isNotNull()
+          .returnsNormally()
           .deepEquals(['exceeds the depth limit of 1000']);
     });
   });
diff --git a/pkgs/checks/test/test_shared.dart b/pkgs/checks/test/test_shared.dart
index 1a30eb55a..932879c49 100644
--- a/pkgs/checks/test/test_shared.dart
+++ b/pkgs/checks/test/test_shared.dart
@@ -16,16 +16,18 @@ extension RejectionChecks<T> on Subject<T> {
       didRunCallback = true;
       final failure = softCheck(value, condition);
       if (failure == null) {
-        return Extracted.rejection(which: [
-          'was accepted by the condition checking:',
-          ...describe(condition)
-        ]);
+        return Extracted.rejection(
+            which: () => [
+                  'was accepted by the condition checking:',
+                  ...describe(condition)
+                ]);
       }
       return Extracted.value(failure.rejection);
     });
     if (didRunCallback) {
       rejection
           .has((r) => r.actual, 'actual')
+          .returnsNormally()
           .deepEquals(actual ?? literal(actualValue));
     } else {
       rejection
@@ -36,7 +38,11 @@ extension RejectionChecks<T> on Subject<T> {
     if (which == null) {
       rejection.has((r) => r.which, 'which').isNull();
     } else {
-      rejection.has((r) => r.which, 'which').isNotNull().deepEquals(which);
+      rejection
+          .has((r) => r.which, 'which')
+          .isNotNull()
+          .returnsNormally()
+          .deepEquals(which);
     }
   }
 
@@ -51,16 +57,17 @@ extension RejectionChecks<T> on Subject<T> {
       didRunCallback = true;
       final failure = await softCheckAsync(value, condition);
       if (failure == null) {
-        return Extracted.rejection(which: [
-          'was accepted by the condition checking:',
-          ...await describeAsync(condition)
-        ]);
+        final description = await describeAsync(condition);
+        return Extracted.rejection(
+            which: () =>
+                ['was accepted by the condition checking:', ...description]);
       }
       return Extracted.value(failure.rejection);
     }));
     if (didRunCallback) {
       rejection
           .has((r) => r.actual, 'actual')
+          .returnsNormally()
           .deepEquals(actual ?? literal(actualValue));
     } else {
       rejection
@@ -71,7 +78,11 @@ extension RejectionChecks<T> on Subject<T> {
     if (which == null) {
       rejection.has((r) => r.which, 'which').isNull();
     } else {
-      rejection.has((r) => r.which, 'which').isNotNull().deepEquals(which);
+      rejection
+          .has((r) => r.which, 'which')
+          .isNotNull()
+          .returnsNormally()
+          .deepEquals(which);
     }
   }
 }