diff --git a/TUnit.Assertions.Tests/Bugs/Issue5613Tests.cs b/TUnit.Assertions.Tests/Bugs/Issue5613Tests.cs new file mode 100644 index 0000000000..10f0fb53ee --- /dev/null +++ b/TUnit.Assertions.Tests/Bugs/Issue5613Tests.cs @@ -0,0 +1,44 @@ +namespace TUnit.Assertions.Tests.Bugs; + +/// +/// Regression tests for GitHub issue #5613 findings #1 and #3: +/// Assert.That(Task task) returns AsyncDelegateAssertion which implements both +/// IAssertionSource<object?> and IAssertionSource<Task>. This ambiguity breaks +/// generic inference on IsNotNull / IsNull / IsSameReferenceAs / IsNotSameReferenceAs +/// (CS0411) and produces a misleading CS1929 pointing at JsonElement. +/// +/// Fix: instance methods on AsyncDelegateAssertion that forward to the +/// IAssertionSource<Task> interface. +/// +public class Issue5613Tests +{ + [Test] + public async Task IsNotNull_On_Task_Reference_Compiles_And_Passes() + { + Task t = Task.CompletedTask; + await Assert.That(t).IsNotNull(); + } + + [Test] + public async Task IsNull_On_Null_Task_Reference_Compiles_And_Passes() + { + Task? t = null; + await Assert.That(t!).IsNull(); + } + + [Test] + public async Task IsSameReferenceAs_On_Task_Compiles_Without_Generic_Annotation() + { + Task a = Task.CompletedTask; + Task b = a; + await Assert.That(a).IsSameReferenceAs(b); + } + + [Test] + public async Task IsNotSameReferenceAs_On_Task_Compiles_Without_Generic_Annotation() + { + Task a = Task.CompletedTask; + Task b = Task.FromResult(true); + await Assert.That(a).IsNotSameReferenceAs(b); + } +} diff --git a/TUnit.Assertions/Sources/AsyncDelegateAssertion.cs b/TUnit.Assertions/Sources/AsyncDelegateAssertion.cs index f24403f399..cfeba43225 100644 --- a/TUnit.Assertions/Sources/AsyncDelegateAssertion.cs +++ b/TUnit.Assertions/Sources/AsyncDelegateAssertion.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Text; using TUnit.Assertions.Conditions; using TUnit.Assertions.Core; @@ -55,6 +56,45 @@ public AsyncDelegateAssertion(Func action, string? expression) // Forwarding methods for Task state assertions // These allow calling task assertions directly on AsyncDelegateAssertion without type inference issues + /// + /// Asserts that the task reference is not null. + /// Without this instance method, Assert.That(task).IsNotNull() would fail generic inference + /// because implements both IAssertionSource<object?> + /// and IAssertionSource<Task>. + /// + public NotNullAssertion IsNotNull() + { + return ((IAssertionSource)this).IsNotNull(); + } + + /// + /// Asserts that the task reference is null. + /// + public TValue_IsNull_Assertion IsNull() + { + return ((IAssertionSource)this).IsNull(); + } + + /// + /// Asserts that the task is the same reference as . + /// + public TValue_IsSameReferenceAs_Object_Assertion IsSameReferenceAs( + object? expected, + [CallerArgumentExpression(nameof(expected))] string? expectedExpression = null) + { + return ((IAssertionSource)this).IsSameReferenceAs(expected, expectedExpression); + } + + /// + /// Asserts that the task is not the same reference as . + /// + public TValue_IsNotSameReferenceAs_Object_Assertion IsNotSameReferenceAs( + object? expected, + [CallerArgumentExpression(nameof(expected))] string? expectedExpression = null) + { + return ((IAssertionSource)this).IsNotSameReferenceAs(expected, expectedExpression); + } + /// /// Asserts that the task is completed. /// diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt index 3fd21b094e..a4a91f9c5b 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -6063,7 +6063,11 @@ namespace .Sources public .<.> IsNotCompleted() { } public .<.> IsNotCompletedSuccessfully() { } public .<.> IsNotFaulted() { } + public .<.> IsNotNull() { } + public ._IsNotSameReferenceAs_Object_Assertion<.> IsNotSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsNotTypeOf() { } + public ._IsNull_Assertion<.> IsNull() { } + public ._IsSameReferenceAs_Object_Assertion<.> IsSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsTypeOf() { } public . Throws( exceptionType) { } public . Throws() diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 137df82a5a..dd2bde74a6 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -5997,7 +5997,11 @@ namespace .Sources public .<.> IsNotCompleted() { } public .<.> IsNotCompletedSuccessfully() { } public .<.> IsNotFaulted() { } + public .<.> IsNotNull() { } + public ._IsNotSameReferenceAs_Object_Assertion<.> IsNotSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsNotTypeOf() { } + public ._IsNull_Assertion<.> IsNull() { } + public ._IsSameReferenceAs_Object_Assertion<.> IsSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsTypeOf() { } public . Throws( exceptionType) { } public . Throws() diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 948eae45c8..9c8872351e 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -6063,7 +6063,11 @@ namespace .Sources public .<.> IsNotCompleted() { } public .<.> IsNotCompletedSuccessfully() { } public .<.> IsNotFaulted() { } + public .<.> IsNotNull() { } + public ._IsNotSameReferenceAs_Object_Assertion<.> IsNotSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsNotTypeOf() { } + public ._IsNull_Assertion<.> IsNull() { } + public ._IsSameReferenceAs_Object_Assertion<.> IsSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsTypeOf() { } public . Throws( exceptionType) { } public . Throws() diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt index e9024b14e6..671d0f1c34 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -5240,7 +5240,11 @@ namespace .Sources public .<.> IsNotCanceled() { } public .<.> IsNotCompleted() { } public .<.> IsNotFaulted() { } + public .<.> IsNotNull() { } + public ._IsNotSameReferenceAs_Object_Assertion<.> IsNotSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsNotTypeOf() { } + public ._IsNull_Assertion<.> IsNull() { } + public ._IsSameReferenceAs_Object_Assertion<.> IsSameReferenceAs(object? expected, [.("expected")] string? expectedExpression = null) { } public . IsTypeOf() { } public . Throws( exceptionType) { } public . Throws()