Summary
When migrating tests from xUnit to MSTest (e.g., dotnet/sdk#54732), there is no clean MSTest equivalent of xUnit''s Assert.All<T>(IEnumerable<T>, Action<T>). That xUnit API runs the inline assertion against every element of a collection and aggregates all failures into a single readable report (including the index of each failing item), instead of stopping at the first one.
The current MSTest workaround is to write a foreach loop:
// Before (xUnit)
Assert.All(group, e => e.Key!.ToString()!.StartsWith(prefix, StringComparison.Ordinal));
// After (MSTest, current)
foreach (DictionaryEntry entry in group)
{
Assert.StartsWith(prefix, entry.Key!.ToString()!, StringComparison.Ordinal);
}
Real-world example from the migration: https://github.com/dotnet/sdk/pull/54732/files#diff-463b1612655ea283a5998dfc2988f2419af9f6e3ae5072d9ba5a6db2e359c71eL47-R51
Drawbacks of the workaround
- Fail-fast only — the test stops at the first bad element, hiding how widespread the issue is. Triage requires multiple rerun-and-fix cycles.
- No index/context in the failure message — the user has to instrument the loop manually (custom message, counter) to know which element failed.
- More noise in test code — a single semantic assertion turns into a multi-line block, which is friction during xUnit→MSTest migrations.
Proposal
Consider adding an Assert.All (name TBD) API along the lines of:
public static void All<T>(IEnumerable<T> collection, Action<T> assertion);
public static void All<T>(IEnumerable<T> collection, Action<T, int> assertion); // with index
public static Task AllAsync<T>(IEnumerable<T> collection, Func<T, Task> assertion);
Semantics (matching xUnit):
- Invokes
assertion for every item.
- Catches
AssertFailedException (and other exceptions) per item, continues iterating.
- Throws a single aggregated
AssertFailedException listing each failing index, the offending item, and the inner failure message.
Open questions
- Naming.
Assert.All matches xUnit and reads naturally, but MSTest already uses Assert.* for single-value assertions. Alternatives: Assert.ForEach, CollectionAssert.All (though we are deprioritizing CollectionAssert.* — see the move toward modern Assert.* APIs).
- Scope. Should this also have a
Single/Contains(IEnumerable, predicate)-style sibling, or is All enough?
- Analyzer support. Should
MSTEST0037-style guidance suggest Assert.All over a hand-written foreach + Assert.*?
Context
Summary
When migrating tests from xUnit to MSTest (e.g., dotnet/sdk#54732), there is no clean MSTest equivalent of xUnit''s
Assert.All<T>(IEnumerable<T>, Action<T>). That xUnit API runs the inline assertion against every element of a collection and aggregates all failures into a single readable report (including the index of each failing item), instead of stopping at the first one.The current MSTest workaround is to write a
foreachloop:Real-world example from the migration: https://github.com/dotnet/sdk/pull/54732/files#diff-463b1612655ea283a5998dfc2988f2419af9f6e3ae5072d9ba5a6db2e359c71eL47-R51
Drawbacks of the workaround
Proposal
Consider adding an
Assert.All(name TBD) API along the lines of:Semantics (matching xUnit):
assertionfor every item.AssertFailedException(and other exceptions) per item, continues iterating.AssertFailedExceptionlisting each failing index, the offending item, and the inner failure message.Open questions
Assert.Allmatches xUnit and reads naturally, but MSTest already usesAssert.*for single-value assertions. Alternatives:Assert.ForEach,CollectionAssert.All(though we are deprioritizingCollectionAssert.*— see the move toward modernAssert.*APIs).Single/Contains(IEnumerable, predicate)-style sibling, or isAllenough?MSTEST0037-style guidance suggestAssert.Allover a hand-writtenforeach + Assert.*?Context
Microsoft.NET.Build.Containers.UnitTests).All,AllAsync).