diff --git a/docs/combinations.md b/docs/combinations.md
index a524e03aa..fc5a98d29 100644
--- a/docs/combinations.md
+++ b/docs/combinations.md
@@ -175,7 +175,7 @@ public Task BuildAddressExceptionsDisabledTest()
city);
}
```
-snippet source | anchor
+snippet source | anchor
@@ -401,7 +401,7 @@ class CustomCombinationConverter :
string.Join(", ", keys.Select(_ => _.Value));
}
```
-snippet source | anchor
+snippet source | anchor
Full control of serialization can be achieved by inheriting from `WriteOnlyJsonConverter`.
@@ -420,7 +420,7 @@ static CustomCombinationConverter customConverter = new();
public static void Init() =>
VerifierSettings.AddExtraSettings(_ => _.Converters.Insert(0, customConverter));
```
-snippet source | anchor
+snippet source | anchor
@@ -551,5 +551,5 @@ Headers can be enabled globally:
public static void EnableIncludeHeaders() =>
CombinationSettings.IncludeHeaders();
```
-snippet source | anchor
+snippet source | anchor
diff --git a/src/StaticSettingsTests/CombinationTests.EveryRegisteredCallbackIsAwaited_run.verified.txt b/src/StaticSettingsTests/CombinationTests.EveryRegisteredCallbackIsAwaited_run.verified.txt
new file mode 100644
index 000000000..f70460ad2
--- /dev/null
+++ b/src/StaticSettingsTests/CombinationTests.EveryRegisteredCallbackIsAwaited_run.verified.txt
@@ -0,0 +1,4 @@
+{
+ list: Result,
+ 1: Exception: from the first before callback
+}
\ No newline at end of file
diff --git a/src/StaticSettingsTests/CombinationTests.cs b/src/StaticSettingsTests/CombinationTests.cs
index 71eea270f..e5a67405d 100644
--- a/src/StaticSettingsTests/CombinationTests.cs
+++ b/src/StaticSettingsTests/CombinationTests.cs
@@ -79,6 +79,43 @@ await Verify(new
.UseMethodName("CallbackResults");
}
+ // Two registrations, for example an extension package plus user code. A multicast
+ // delegate would return only the last target's Task, leaving the first to run
+ // unawaited: its exceptions unobserved and its work racing the combination method.
+ [Fact]
+ public async Task EveryRegisteredCallbackIsAwaited()
+ {
+ var exceptionMessages = new List();
+
+ // Fails only after an await, so the failure is carried by the returned Task rather
+ // than thrown synchronously. Registered first, so a multicast delegate would return
+ // the second registration's Task instead and this one would never be observed.
+ CombinationSettings.UseCallbacks(
+ async _ =>
+ {
+ await Task.Yield();
+ throw new("from the first before callback");
+ },
+ (_, _) => Task.CompletedTask,
+ (_, exception) =>
+ {
+ exceptionMessages.Add(exception.Message);
+ return Task.CompletedTask;
+ });
+
+ CombinationSettings.UseCallbacks(
+ _ => Task.CompletedTask,
+ (_, _) => Task.CompletedTask,
+ (_, _) => Task.CompletedTask);
+
+ int[] list = [1];
+ await Combination()
+ .Verify((int param1) => param1, list)
+ .UseMethodName("EveryRegisteredCallbackIsAwaited_run");
+
+ Assert.Equal(["from the first before callback"], exceptionMessages);
+ }
+
[Fact]
public async Task AfterCallbackRawValueWhenRecording()
{
diff --git a/src/Verify/Combinations/CombinationSettings.cs b/src/Verify/Combinations/CombinationSettings.cs
index 0ef3ab9d1..2f0372107 100644
--- a/src/Verify/Combinations/CombinationSettings.cs
+++ b/src/Verify/Combinations/CombinationSettings.cs
@@ -1,4 +1,4 @@
-namespace VerifyTests;
+namespace VerifyTests;
public delegate Task BeforeCombination(IReadOnlyList