Skip to content
Prev Previous commit
Next Next commit
fix: add XML docs for SAI type-safety loss and tighten WaitsFor test …
…assertions

Document the object? type erasure on generated setup methods for
static-abstract-interface returns (CS8920) and restore stricter
timeout/assertion values in WaitsForAssertionTests.
  • Loading branch information
Lucas Chaves committed Mar 17, 2026
commit 6def2f32a3f22b511aa899c64183216554b3d2e5
21 changes: 10 additions & 11 deletions TUnit.Assertions.Tests/WaitsForAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public async Task WaitsFor_With_Custom_Polling_Interval()

await Assert.That(getValue).WaitsFor(
assert => assert.IsGreaterThan(2),
timeout: TimeSpan.FromSeconds(30),
timeout: TimeSpan.FromSeconds(1),
pollingInterval: TimeSpan.FromMilliseconds(50));

stopwatch.Stop();
Expand Down Expand Up @@ -249,21 +249,20 @@ public async Task WaitsFor_Performance_Many_Quick_Polls()
// This will take many polls before succeeding
Func<int> getValue = () => Interlocked.Increment(ref counter);

// Each polling iteration has non-trivial overhead from the assertion pipeline
// (creating assertion objects, evaluating, exception handling), so use a generous
// timeout and moderate target to avoid flakiness on slow CI environments.
// Use a more realistic polling interval (10ms) and target count (20)
// On .NET Framework, the minimum timer resolution is ~15ms, making 1ms intervals unreliable
await Assert.That(getValue).WaitsFor(
assert => assert.IsGreaterThan(5),
timeout: TimeSpan.FromSeconds(30),
pollingInterval: TimeSpan.FromMilliseconds(50));
assert => assert.IsGreaterThan(20),
timeout: TimeSpan.FromSeconds(5),
pollingInterval: TimeSpan.FromMilliseconds(10));

stopwatch.Stop();

// Should have made at least 5 attempts
await Assert.That(counter).IsGreaterThanOrEqualTo(6);
// Should have made at least 20 attempts
await Assert.That(counter).IsGreaterThanOrEqualTo(21);

// Should complete well under the timeout
await Assert.That(stopwatch.Elapsed).IsLessThan(TimeSpan.FromSeconds(30));
// Should complete in a reasonable time (well under 5 seconds)
await Assert.That(stopwatch.Elapsed).IsLessThan(TimeSpan.FromSeconds(5));
}

[Test]
Expand Down
20 changes: 20 additions & 0 deletions TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,16 @@ private static void EmitMemberMethodBody(CodeWriter writer, MockMemberModel meth
var extensionParam = $"this global::TUnit.Mocks.Mock<{mockableType}> mock";
var fullParamList = string.IsNullOrEmpty(paramList) ? extensionParam : $"{extensionParam}, {paramList}";

if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"/// <summary>Configure the mock setup for <c>{method.Name}</c>.</summary>");
writer.AppendLine($"/// <returns>");
writer.AppendLine($"/// A <see cref=\"global::TUnit.Mocks.MockMethodCall{{T}}\"/> typed as <c>object?</c> because the return type");
writer.AppendLine($"/// contains static abstract members and cannot be used as a generic type argument (CS8920).");
writer.AppendLine($"/// Pass a value that implements the return interface to <c>.Returns()</c> — incorrect types will cause an <see cref=\"global::System.InvalidCastException\"/> at call time.");
writer.AppendLine($"/// </returns>");
}

using (writer.Block($"public static {returnType} {safeMemberName}{typeParams}({fullParamList}){constraints}"))
{
// Build matchers array
Expand Down Expand Up @@ -712,6 +722,16 @@ private static void EmitSingleFuncOverload(CodeWriter writer, MockMemberModel me
var extensionParam = $"this global::TUnit.Mocks.Mock<{mockableType}> mock";
var fullParamList = string.IsNullOrEmpty(paramList) ? extensionParam : $"{extensionParam}, {paramList}";

if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"/// <summary>Configure the mock setup for <c>{method.Name}</c>.</summary>");
writer.AppendLine($"/// <returns>");
writer.AppendLine($"/// A <see cref=\"global::TUnit.Mocks.MockMethodCall{{T}}\"/> typed as <c>object?</c> because the return type");
writer.AppendLine($"/// contains static abstract members and cannot be used as a generic type argument (CS8920).");
writer.AppendLine($"/// Pass a value that implements the return interface to <c>.Returns()</c> — incorrect types will cause an <see cref=\"global::System.InvalidCastException\"/> at call time.");
writer.AppendLine($"/// </returns>");
}

using (writer.Block($"public static {returnType} {safeMemberName}{typeParams}({fullParamList}){constraints}"))
{
// Convert Func params to Arg<T> via implicit conversion
Expand Down
Loading