diff --git a/TUnit.Assertions.Tests/CollectionNullabilityWarningTests.cs b/TUnit.Assertions.Tests/CollectionNullabilityWarningTests.cs
new file mode 100644
index 0000000000..bf97d635ec
--- /dev/null
+++ b/TUnit.Assertions.Tests/CollectionNullabilityWarningTests.cs
@@ -0,0 +1,299 @@
+namespace TUnit.Assertions.Tests;
+
+///
+/// Tests to ensure that Assert.That() accepts nullable collection types without generating
+/// nullability warnings (CS8604, CS8625, etc.). This validates the fix for GitHub issue reporting
+/// false positive nullability warnings for List, IList, Dictionary, IDictionary types.
+///
+public class CollectionNullabilityWarningTests
+{
+ // ===================================
+ // List? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableList_AcceptsNullableValue_NoWarning()
+ {
+ List? list = ["a", "b", "c"];
+ await Assert.That(list).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableList_WithNullValue_IsNotNull_Fails()
+ {
+ List? list = null;
+ var action = async () => await Assert.That(list).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableList_WithNullValue_IsNull_Passes()
+ {
+ List? list = null;
+ await Assert.That(list).IsNull();
+ }
+
+ // ===================================
+ // IList? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableIList_AcceptsNullableValue_NoWarning()
+ {
+ IList? list = new List { "a", "b", "c" };
+ await Assert.That(list).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableIList_WithNullValue_IsNotNull_Fails()
+ {
+ IList? list = null;
+ var action = async () => await Assert.That(list).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableIList_WithNullValue_IsNull_Passes()
+ {
+ IList? list = null;
+ await Assert.That(list).IsNull();
+ }
+
+ // ===================================
+ // Dictionary? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableDictionary_AcceptsNullableValue_NoWarning()
+ {
+ Dictionary? dict = new() { ["key"] = 1 };
+ await Assert.That(dict).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableDictionary_WithNullValue_IsNotNull_Fails()
+ {
+ Dictionary? dict = null;
+ var action = async () => await Assert.That(dict).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableDictionary_WithNullValue_IsNull_Passes()
+ {
+ Dictionary? dict = null;
+ await Assert.That(dict).IsNull();
+ }
+
+ // ===================================
+ // IDictionary? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableIDictionary_AcceptsNullableValue_NoWarning()
+ {
+ IDictionary? dict = new Dictionary { ["key"] = 1 };
+ await Assert.That(dict).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableIDictionary_WithNullValue_IsNotNull_Fails()
+ {
+ IDictionary? dict = null;
+ var action = async () => await Assert.That(dict).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableIDictionary_WithNullValue_IsNull_Passes()
+ {
+ IDictionary? dict = null;
+ await Assert.That(dict).IsNull();
+ }
+
+ // ===================================
+ // IReadOnlyDictionary? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableIReadOnlyDictionary_AcceptsNullableValue_NoWarning()
+ {
+ IReadOnlyDictionary? dict = new Dictionary { ["key"] = 1 };
+ await Assert.That(dict).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableIReadOnlyDictionary_WithNullValue_IsNotNull_Fails()
+ {
+ IReadOnlyDictionary? dict = null;
+ var action = async () => await Assert.That(dict).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableIReadOnlyDictionary_WithNullValue_IsNull_Passes()
+ {
+ IReadOnlyDictionary? dict = null;
+ await Assert.That(dict).IsNull();
+ }
+
+ // ===================================
+ // Array? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableArray_AcceptsNullableValue_NoWarning()
+ {
+ string[]? arr = ["a", "b", "c"];
+ await Assert.That(arr).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableArray_WithNullValue_IsNotNull_Fails()
+ {
+ string[]? arr = null;
+ var action = async () => await Assert.That(arr).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableArray_WithNullValue_IsNull_Passes()
+ {
+ string[]? arr = null;
+ await Assert.That(arr).IsNull();
+ }
+
+ // ===================================
+ // ISet? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableISet_AcceptsNullableValue_NoWarning()
+ {
+ ISet? set = new HashSet { "a", "b", "c" };
+ await Assert.That(set).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableISet_WithNullValue_IsNotNull_Fails()
+ {
+ ISet? set = null;
+ var action = async () => await Assert.That(set).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableISet_WithNullValue_IsNull_Passes()
+ {
+ ISet? set = null;
+ await Assert.That(set).IsNull();
+ }
+
+ // ===================================
+ // HashSet? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableHashSet_AcceptsNullableValue_NoWarning()
+ {
+ HashSet? set = ["a", "b", "c"];
+ await Assert.That(set).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableHashSet_WithNullValue_IsNotNull_Fails()
+ {
+ HashSet? set = null;
+ var action = async () => await Assert.That(set).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableHashSet_WithNullValue_IsNull_Passes()
+ {
+ HashSet? set = null;
+ await Assert.That(set).IsNull();
+ }
+
+#if NET5_0_OR_GREATER
+ // ===================================
+ // IReadOnlySet? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableIReadOnlySet_AcceptsNullableValue_NoWarning()
+ {
+ IReadOnlySet? set = new HashSet { "a", "b", "c" };
+ await Assert.That(set).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableIReadOnlySet_WithNullValue_IsNotNull_Fails()
+ {
+ IReadOnlySet? set = null;
+ var action = async () => await Assert.That(set).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableIReadOnlySet_WithNullValue_IsNull_Passes()
+ {
+ IReadOnlySet? set = null;
+ await Assert.That(set).IsNull();
+ }
+#endif
+
+ // ===================================
+ // IReadOnlyList? Tests
+ // ===================================
+
+ [Test]
+ public async Task NullableIReadOnlyList_AcceptsNullableValue_NoWarning()
+ {
+ IReadOnlyList? list = new List { "a", "b", "c" };
+ await Assert.That(list).IsNotNull();
+ }
+
+ [Test]
+ public async Task NullableIReadOnlyList_WithNullValue_IsNotNull_Fails()
+ {
+ IReadOnlyList? list = null;
+ var action = async () => await Assert.That(list).IsNotNull();
+ await Assert.That(action).Throws();
+ }
+
+ [Test]
+ public async Task NullableIReadOnlyList_WithNullValue_IsNull_Passes()
+ {
+ IReadOnlyList? list = null;
+ await Assert.That(list).IsNull();
+ }
+
+ // ===================================
+ // Method parameter tests (original issue scenario)
+ // ===================================
+
+ [Test]
+ public async Task MethodWithNullableListParameter_NoWarning()
+ {
+ List? list = ["test"];
+ await VerifyNotNull(list);
+ }
+
+ [Test]
+ public async Task MethodWithNullableIDictionaryParameter_NoWarning()
+ {
+ IDictionary? dict = new Dictionary { ["key"] = 1 };
+ await VerifyDictionaryNotNull(dict);
+ }
+
+ private static async Task VerifyNotNull(List? actual)
+ {
+ await Assert.That(actual).IsNotNull();
+ }
+
+ private static async Task VerifyDictionaryNotNull(IDictionary? actual)
+ {
+ await Assert.That(actual).IsNotNull();
+ }
+}
diff --git a/TUnit.Assertions/Extensions/Assert.cs b/TUnit.Assertions/Extensions/Assert.cs
index 389202f39a..d79d879444 100644
--- a/TUnit.Assertions/Extensions/Assert.cs
+++ b/TUnit.Assertions/Extensions/Assert.cs
@@ -21,7 +21,7 @@ public static class Assert
///
[OverloadResolutionPriority(3)]
public static DictionaryAssertion That(
- IReadOnlyDictionary value,
+ IReadOnlyDictionary? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
where TKey : notnull
{
@@ -35,7 +35,7 @@ public static DictionaryAssertion That(
///
[OverloadResolutionPriority(2)]
public static MutableDictionaryAssertion That(
- IDictionary value,
+ IDictionary? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
where TKey : notnull
{
@@ -108,7 +108,7 @@ public static ReadOnlyMemoryAssertion That(
///
[OverloadResolutionPriority(2)]
public static ReadOnlySetAssertion That(
- IReadOnlySet value,
+ IReadOnlySet? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
{
return new ReadOnlySetAssertion(value, expression ?? "set");
@@ -138,7 +138,7 @@ public static AsyncEnumerableAssertion That(
///
[OverloadResolutionPriority(2)]
public static SetAssertion That(
- ISet value,
+ ISet? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
{
return new SetAssertion(value, expression ?? "set");
@@ -153,7 +153,7 @@ public static SetAssertion That(
///
[OverloadResolutionPriority(4)]
public static ListAssertion That(
- IList value,
+ IList? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
{
return new ListAssertion(value, expression ?? "list");
@@ -166,7 +166,7 @@ public static ListAssertion That(
///
[OverloadResolutionPriority(5)]
public static CollectionAssertion That(
- TItem[] value,
+ TItem[]? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
{
return new CollectionAssertion(value!, expression);
@@ -181,7 +181,7 @@ public static CollectionAssertion That(
///
[OverloadResolutionPriority(3)]
public static ReadOnlyListAssertion That(
- IReadOnlyList value,
+ IReadOnlyList? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
{
return new ReadOnlyListAssertion(value, expression ?? "readOnlyList");
@@ -195,7 +195,7 @@ public static ReadOnlyListAssertion That(
///
[OverloadResolutionPriority(3)]
public static HashSetAssertion That(
- HashSet value,
+ HashSet? value,
[CallerArgumentExpression(nameof(value))] string? expression = null)
{
return new HashSetAssertion(value, expression ?? "hashSet");
diff --git a/TUnit.Assertions/Sources/DictionaryAssertion.cs b/TUnit.Assertions/Sources/DictionaryAssertion.cs
index b80c36a8a5..d08ef3fcb5 100644
--- a/TUnit.Assertions/Sources/DictionaryAssertion.cs
+++ b/TUnit.Assertions/Sources/DictionaryAssertion.cs
@@ -13,17 +13,17 @@ namespace TUnit.Assertions.Sources;
public class DictionaryAssertion : DictionaryAssertionBase, TKey, TValue>
where TKey : notnull
{
- public DictionaryAssertion(IReadOnlyDictionary value, string? expression)
+ public DictionaryAssertion(IReadOnlyDictionary? value, string? expression)
: base(CreateContext(value, expression))
{
}
private static AssertionContext> CreateContext(
- IReadOnlyDictionary value,
+ IReadOnlyDictionary? value,
string? expression)
{
var expressionBuilder = new StringBuilder();
expressionBuilder.Append($"Assert.That({expression ?? "?"})");
- return new AssertionContext>(value, expressionBuilder);
+ return new AssertionContext>(value!, expressionBuilder);
}
}
diff --git a/TUnit.Assertions/Sources/ListAssertion.cs b/TUnit.Assertions/Sources/ListAssertion.cs
index 62a26e29b1..0dc4c587ea 100644
--- a/TUnit.Assertions/Sources/ListAssertion.cs
+++ b/TUnit.Assertions/Sources/ListAssertion.cs
@@ -11,7 +11,7 @@ namespace TUnit.Assertions.Sources;
///
public class ListAssertion : ListAssertionBase, TItem>
{
- public ListAssertion(IList value, string? expression)
+ public ListAssertion(IList? value, string? expression)
: base(CreateContext(value, expression))
{
}
@@ -22,11 +22,11 @@ internal ListAssertion(AssertionContext> context)
}
private static AssertionContext> CreateContext(
- IList value,
+ IList? value,
string? expression)
{
var expressionBuilder = new StringBuilder();
expressionBuilder.Append($"Assert.That({expression ?? "?"})");
- return new AssertionContext>(value, expressionBuilder);
+ return new AssertionContext>(value!, expressionBuilder);
}
}
diff --git a/TUnit.Assertions/Sources/MutableDictionaryAssertion.cs b/TUnit.Assertions/Sources/MutableDictionaryAssertion.cs
index 1ef7569fcd..2d0afd5ae0 100644
--- a/TUnit.Assertions/Sources/MutableDictionaryAssertion.cs
+++ b/TUnit.Assertions/Sources/MutableDictionaryAssertion.cs
@@ -13,17 +13,17 @@ namespace TUnit.Assertions.Sources;
public class MutableDictionaryAssertion : MutableDictionaryAssertionBase, TKey, TValue>
where TKey : notnull
{
- public MutableDictionaryAssertion(IDictionary value, string? expression)
+ public MutableDictionaryAssertion(IDictionary? value, string? expression)
: base(CreateContext(value, expression))
{
}
private static AssertionContext> CreateContext(
- IDictionary value,
+ IDictionary? value,
string? expression)
{
var expressionBuilder = new StringBuilder();
expressionBuilder.Append($"Assert.That({expression ?? "?"})");
- return new AssertionContext>(value, expressionBuilder);
+ return new AssertionContext>(value!, expressionBuilder);
}
}
diff --git a/TUnit.Assertions/Sources/SetAssertion.cs b/TUnit.Assertions/Sources/SetAssertion.cs
index 0a44a498cf..155b306051 100644
--- a/TUnit.Assertions/Sources/SetAssertion.cs
+++ b/TUnit.Assertions/Sources/SetAssertion.cs
@@ -15,8 +15,8 @@ public class SetAssertion : SetAssertionBase, TItem>, IAssert
///
/// Creates a new SetAssertion for the given set.
///
- public SetAssertion(ISet value, string expression)
- : base(new AssertionContext>(new EvaluationContext>(value), CreateExpressionBuilder(expression)))
+ public SetAssertion(ISet? value, string expression)
+ : base(new AssertionContext>(new EvaluationContext>(value!), CreateExpressionBuilder(expression)))
{
}
@@ -53,8 +53,8 @@ public class ReadOnlySetAssertion : SetAssertionBase,
///
/// Creates a new ReadOnlySetAssertion for the given read-only set.
///
- public ReadOnlySetAssertion(IReadOnlySet value, string expression)
- : base(new AssertionContext>(new EvaluationContext>(value), CreateExpressionBuilder(expression)))
+ public ReadOnlySetAssertion(IReadOnlySet? value, string expression)
+ : base(new AssertionContext>(new EvaluationContext>(value!), CreateExpressionBuilder(expression)))
{
}
@@ -91,8 +91,8 @@ public class HashSetAssertion : SetAssertionBase, TItem>,
///
/// Creates a new HashSetAssertion for the given hash set.
///
- public HashSetAssertion(HashSet value, string expression)
- : base(new AssertionContext>(new EvaluationContext>(value), CreateExpressionBuilder(expression)))
+ public HashSetAssertion(HashSet? value, string expression)
+ : base(new AssertionContext>(new EvaluationContext>(value!), CreateExpressionBuilder(expression)))
{
}
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 601778c174..5daa46f3f4 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
@@ -181,19 +181,19 @@ namespace
[.(2)]
public static . That(string? value, [.("value")] string? expression = null) { }
[.(3)]
- public static . That(. value, [.("value")] string? expression = null) { }
+ public static . That(.? value, [.("value")] string? expression = null) { }
[.(1)]
public static . That(. value, [.("value")] string? expression = null) { }
[.(1)]
public static . That(.? value, [.("value")] string? expression = null) { }
[.(4)]
- public static . That(. value, [.("value")] string? expression = null) { }
+ public static . That(.? value, [.("value")] string? expression = null) { }
[.(3)]
- public static . That(. value, [.("value")] string? expression = null) { }
+ public static . That(.? value, [.("value")] string? expression = null) { }
[.(2)]
- public static . That(. value, [.("value")] string? expression = null) { }
+ public static . That(.? value, [.("value")] string? expression = null) { }
[.(2)]
- public static . That(. value, [.("value")] string? expression = null) { }
+ public static . That(.? value, [.("value")] string? expression = null) { }
[.(1)]
public static . That(<.?> func, [.("func")] string? expression = null) { }
[.(1)]
@@ -206,13 +206,13 @@ namespace
public static . That( value, [.("value")] string? expression = null) { }
public static . That(. task, [.("task")] string? expression = null) { }
[.(5)]
- public static . That(TItem[] value, [.("value")] string? expression = null) { }
+ public static . That(TItem[]? value, [.("value")] string? expression = null) { }
public static . That(TValue? value, [.("value")] string? expression = null) { }
[.(2)]
- public static . That(. value, [.("value")] string? expression = null)
+ public static . That(.? value, [.("value")] string? expression = null)
where TKey : notnull { }
[.(3)]
- public static . That(. value, [.("value")] string? expression = null)
+ public static . That(.? value, [.("value")] string? expression = null)
where TKey : notnull { }
public static Throws( exceptionType, action) { }
public static TException Throws( action)
@@ -5440,7 +5440,7 @@ namespace .Sources
public class DictionaryAssertion : .<., TKey, TValue>
where TKey : notnull
{
- public DictionaryAssertion(. value, string? expression) { }
+ public DictionaryAssertion(.? value, string? expression) { }
}
public class FuncAssertion : ., ., .
{
@@ -5465,7 +5465,7 @@ namespace .Sources
}
public class HashSetAssertion : .<., TItem>, ., .<.>
{
- public HashSetAssertion(. value, string expression) { }
+ public HashSetAssertion(.? value, string expression) { }
protected override . CreateSetAdapter(. value) { }
}
public abstract class ListAssertionBase : .
@@ -5481,7 +5481,7 @@ namespace .Sources
}
public class ListAssertion : .<., TItem>
{
- public ListAssertion(. value, string? expression) { }
+ public ListAssertion(.? value, string? expression) { }
}
public class MemoryAndContinuation : .
{
@@ -5541,7 +5541,7 @@ namespace .Sources
public class MutableDictionaryAssertion : .<., TKey, TValue>
where TKey : notnull
{
- public MutableDictionaryAssertion(. value, string? expression) { }
+ public MutableDictionaryAssertion(.? value, string? expression) { }
}
public abstract class ReadOnlyListAssertionBase : .
where TList : .
@@ -5565,7 +5565,7 @@ namespace .Sources
}
public class ReadOnlySetAssertion : .<.