Skip to content

Add Assert.Scope() for soft assertions#7355

Draft
Evangelink wants to merge 2 commits intomainfrom
dev/amauryleve/soft-assertion
Draft

Add Assert.Scope() for soft assertions#7355
Evangelink wants to merge 2 commits intomainfrom
dev/amauryleve/soft-assertion

Conversation

@Evangelink
Copy link
Member

Fixes #571

@@ -37,8 +37,19 @@ private Assert()
[DoesNotReturn]
[StackTraceHidden]
internal static void ThrowAssertFailed(string assertionName, string? message)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this PR and #2033, I think it'd be good to expose a "ReportFailure" method that could be used by devs extending MSTest assertions so they would benefits from the various features (launch debugger, soft assertions....). I am not doing it here because we can decide not to go with it for now.

if (scope is not null)
{
scope.AddError(assertionFailedException);
#pragma warning disable CS8763 // A method marked [DoesNotReturn] should not return.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Youssef1313 I don't know what we prefer but it feels better for me to say we continue to help compiler with the default behavior and we know we may have some FPs when code is used under assertion scope. It would otherwise be a breaking change for many people if we had to update all assertion APIs to no longer respect some of these DoesNotReturn compilation indication.

/// // AssertFailedException is thrown here with all collected failures.
/// </code>
/// </example>
public static IDisposable Scope() => new AssertScope();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public API is quite limited, do we want to make it experimental still?

Copy link
Member

@Youssef1313 Youssef1313 Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine for me (and probably best) to be experimental, but curious what exactly do you think is limited.


internal AssertScope()
{
_previousScope = CurrentScope.Value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow nested scopes? I don't personally see a good use case.

/// Adds an assertion failure message to the current scope.
/// </summary>
/// <param name="error">The assertion failure message.</param>
internal void AddError(AssertFailedException error) => _errors.Add(error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider throwing ObjectDisposedException here if _disposed is true.

_disposed = true;
CurrentScope.Value = _previousScope;

if (_errors.Count > 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we special case when _errors.Count == 1 so we don't create AggregateException?

{
private static readonly AsyncLocal<AssertScope?> CurrentScope = new();

private readonly List<AssertFailedException> _errors = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use a thread-safe collection here?

For example, in theory user can create a scope, then create X threads and starts them, then each thread does some piece of work and asserts some result. Then waits for all those threads to finish before disposing the scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Is there any way to soft assert in MSTEST just like verify assert in TESTNG

2 participants