Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions TUnit.Assertions.Tests/Bugs/Issue3633Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace TUnit.Assertions.Tests.Bugs;

public class Issue3633Tests
{
[Test]
public async Task IsNotNull_Should_Throw_Immediately_Outside_Scope()
{
string? nullValue = null;
var exceptionThrown = false;

try
{
await Assert.That(nullValue).IsNotNull();
}
catch (TUnit.Assertions.Exceptions.AssertionException)
{
exceptionThrown = true;
}

await Assert.That(exceptionThrown).IsTrue();
}

[Test]
public async Task IsNotNull_Should_Throw_Before_NullReferenceException()
{
string? nullValue = null;

await Assert.That(async () =>
{
await Assert.That(nullValue).IsNotNull();

var length = nullValue.Length;
}).ThrowsExactly<TUnit.Assertions.Exceptions.AssertionException>();
}

[Test]
public async Task IsNotNull_Should_Return_NonNull_Value_When_Assertion_Passes()
{
string? nonNullValue = "test";

var result = await Assert.That(nonNullValue).IsNotNull();

if (result is null)
{
throw new Exception("IsNotNull returned null even though assertion passed!");
}

await Assert.That(result.Length).IsEqualTo(4);
}

[Test]
public async Task IsNotNull_Should_Not_Stop_Execution_Inside_AssertMultiple()
{
var secondAssertionReached = false;

await Assert.That(async () =>
{
using (Assert.Multiple())
{
string? nullValue = null;
await Assert.That(nullValue).IsNotNull();

secondAssertionReached = true;

await Assert.That(1).IsEqualTo(2);
}
}).ThrowsException();

await Assert.That(secondAssertionReached).IsTrue();
}
}
3 changes: 1 addition & 2 deletions TUnit.Assertions/Conditions/NullAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TValue> m

private async Task<TValue> GetNonNullValueAsync()
{
var (value, _) = await Context.GetAsync();

var value = await AssertAsync();
return value!;
}
}
Expand Down
61 changes: 61 additions & 0 deletions TUnit.TestProject/Bugs/3633/Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace TUnit.TestProject.Bugs._3633;

public class Tests
{
[Test]
public async Task Assertion_Should_Stop_Execution_When_Failing_Outside_AssertMultiple()
{
string? nullValue = null;

// This should throw immediately and stop execution
await Assert.That(nullValue).IsNotNull();

// This line should NEVER be reached because the assertion above should have thrown
var length = nullValue!.Length; // This would cause NullReferenceException if reached

await Assert.That(length).IsEqualTo(0);
}

[Test]
public async Task Assertion_Should_Not_Stop_Execution_When_Failing_Inside_AssertMultiple()
{
var valueReached = false;

await Assert.That(async () =>
{
using (Assert.Multiple())
{
string? nullValue = null;

// This should NOT throw immediately - should accumulate
await Assert.That(nullValue).IsNotNull();

// This line SHOULD be reached even though assertion above failed
valueReached = true;

await Assert.That(1).IsEqualTo(2);
}
}).ThrowsException();

// Verify that code after first assertion was executed
await Assert.That(valueReached).IsTrue();
}

[Test]
public async Task IsNotNull_Should_Throw_Immediately_Outside_Scope()
{
string? nullValue = null;
var exceptionThrown = false;

try
{
await Assert.That(nullValue).IsNotNull();
}
catch (TUnit.Assertions.Exceptions.AssertionException)
{
exceptionThrown = true;
}

await Assert.That(exceptionThrown).IsTrue();
}
}
6 changes: 6 additions & 0 deletions docs/docs/assertions/scopes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ In TUnit you can create an assertion scope by calling `Assert.Multiple()`. This

This is useful for asserting multiple properties and showing all errors at once, instead of having to fix > rerun > fix > rerun.

## Behavior Differences

**Outside `Assert.Multiple()`**: Assertions throw immediately when they fail, stopping test execution.

**Inside `Assert.Multiple()`**: Assertions accumulate failures and only throw when the scope exits, allowing all assertions within the scope to execute.

Implicit Scope:

```csharp
Expand Down
Loading