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
29 changes: 29 additions & 0 deletions TUnit.Assertions.Tests/WaitsForAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,35 @@ public async Task WaitsFor_GitHub_Issue_Example_Scenario()
}
}

[Test]
public async Task WaitsFor_WithIsNotNull_ReturnsResolvedValue_Issue3623()
{
// Regression test for GitHub issue #3623
// WaitsFor was returning null instead of the resolved value when using IsNotNull()
TestEntity? currentValue = null;
var callCount = 0;

Func<TestEntity?> getEntity = () =>
{
callCount++;
if (callCount >= 3)
{
currentValue = new TestEntity { Id = 42, Name = "Resolved", IsReady = true };
}
return currentValue;
};

// This should wait until the entity is not null, then return the non-null entity
TestEntity? entity = await Assert.That(getEntity)
.WaitsFor(e => e.IsNotNull(), TimeSpan.FromSeconds(5));

// The bug was that entity would be null here, even though WaitsFor succeeded
await Assert.That(entity).IsNotNull();
await Assert.That(entity!.Id).IsEqualTo(42);
await Assert.That(entity.Name).IsEqualTo("Resolved");
await Assert.That(entity.IsReady).IsEqualTo(true);
}

// Helper class for testing complex objects
private class TestEntity
{
Expand Down
14 changes: 4 additions & 10 deletions TUnit.Assertions/Conditions/WaitsForAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ protected override async Task<AssertionResult> CheckAsync(EvaluationMetadata<TVa
var assertion = _assertionBuilder(assertionSource);
await assertion.AssertAsync();

// Store the successfully resolved value so it can be returned
_resolvedValue = currentValue;
return AssertionResult.Passed;
}
catch (AssertionException ex)
Expand Down Expand Up @@ -92,12 +90,6 @@ protected override async Task<AssertionResult> CheckAsync(EvaluationMetadata<TVa
$"assertion did not pass within {_timeout.TotalMilliseconds:F0}ms after {attemptCount} attempts. {lastErrorMessage}");
}

/// <summary>
/// The resolved value after the assertion passes.
/// This allows users to capture and use the value in downstream assertions.
/// </summary>
private TValue? _resolvedValue;

/// <summary>
/// Executes the assertion and returns the resolved value upon success.
/// This enables the pattern: Entity entity = await Assert.That(getEntity).WaitsFor(...);
Expand All @@ -107,8 +99,10 @@ protected override async Task<AssertionResult> CheckAsync(EvaluationMetadata<TVa
// Execute the base assertion logic (which calls CheckAsync and handles the result)
await base.AssertAsync();

// Return the resolved value that was stored when the assertion passed
return _resolvedValue;
// After CheckAsync succeeds, the context contains the updated value
// from the successful ReevaluateAsync call
var (value, _) = await Context.GetAsync();
return value;
}

protected override string GetExpectation() =>
Expand Down
Loading