Skip to content

Proposal: Add Assert.All<T> (aggregating per-element assertion) as an MSTest equivalent of xUnit's Assert.All #9075

@Evangelink

Description

@Evangelink

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

  1. 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).
  2. Scope. Should this also have a Single/Contains(IEnumerable, predicate)-style sibling, or is All enough?
  3. Analyzer support. Should MSTEST0037-style guidance suggest Assert.All over a hand-written foreach + Assert.*?

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions