Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat(assertions): re-add some lost assertions
  • Loading branch information
thomhurst committed Oct 21, 2025
commit 22f944d66be1460727593ccfbfe62cac85d2c823
46 changes: 46 additions & 0 deletions TUnit.Assertions.Tests/BooleanAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,50 @@ public async Task Test_Boolean_IsFalse_FromExpression()
var result = 5 < 3;
await Assert.That(result).IsFalse();
}

[Test]
public async Task Test_NullableBoolean_IsTrue_WithTrue()
{
bool? value = true;
await Assert.That(value).IsTrue();
}

[Test]
public async Task Test_NullableBoolean_IsTrue_WithFalse()
{
bool? value = false;
await Assert.That(async () => await Assert.That(value).IsTrue())
.Throws<AssertionException>();
}

[Test]
public async Task Test_NullableBoolean_IsTrue_WithNull()
{
bool? value = null;
await Assert.That(async () => await Assert.That(value).IsTrue())
.Throws<AssertionException>();
}

[Test]
public async Task Test_NullableBoolean_IsFalse_WithFalse()
{
bool? value = false;
await Assert.That(value).IsFalse();
}

[Test]
public async Task Test_NullableBoolean_IsFalse_WithTrue()
{
bool? value = true;
await Assert.That(async () => await Assert.That(value).IsFalse())
.Throws<AssertionException>();
}

[Test]
public async Task Test_NullableBoolean_IsFalse_WithNull()
{
bool? value = null;
await Assert.That(async () => await Assert.That(value).IsFalse())
.Throws<AssertionException>();
}
}
75 changes: 75 additions & 0 deletions TUnit.Assertions.Tests/Old/DefaultAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,79 @@ public async Task IsNotDefault_ValueType_DateTime_NotDefault()
var dt = DateTime.Now;
await TUnitAssert.That(dt).IsNotDefault();
}

[Test]
public async Task IsDefault_ReferenceType_String_Null()
{
string? str = null;
await TUnitAssert.That(str).IsDefault();
}

[Test]
public async Task IsDefault_ReferenceType_String_NotNull()
{
await TUnitAssert.That(async () =>
{
string str = "Hello";
await TUnitAssert.That(str).IsDefault();
}).Throws<TUnitAssertionException>();
}

[Test]
public async Task IsDefault_ReferenceType_Object_Null()
{
object? obj = null;
await TUnitAssert.That(obj).IsDefault();
}

[Test]
public async Task IsDefault_ReferenceType_Object_NotNull()
{
await TUnitAssert.That(async () =>
{
object obj = new object();
await TUnitAssert.That(obj).IsDefault();
}).Throws<TUnitAssertionException>();
}

[Test]
public async Task IsNotDefault_ReferenceType_String_Null()
{
await TUnitAssert.That(async () =>
{
string? str = null;
await TUnitAssert.That(str).IsNotDefault();
}).Throws<TUnitAssertionException>();
}

[Test]
public async Task IsNotDefault_ReferenceType_String_NotNull()
{
string str = "Hello";
await TUnitAssert.That(str).IsNotDefault();
}

[Test]
public async Task IsNotDefault_ReferenceType_String_Empty()
{
string str = "";
await TUnitAssert.That(str).IsNotDefault();
}

[Test]
public async Task IsNotDefault_ReferenceType_Object_Null()
{
await TUnitAssert.That(async () =>
{
object? obj = null;
await TUnitAssert.That(obj).IsNotDefault();
}).Throws<TUnitAssertionException>();
}

[Test]
public async Task IsNotDefault_ReferenceType_Object_NotNull()
{
object obj = new object();
await TUnitAssert.That(obj).IsNotDefault();
}
}
38 changes: 38 additions & 0 deletions TUnit.Assertions.Tests/StringNullabilityAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,42 @@ public async Task IsNotNullOrEmpty_WithNonEmptyString_Passes()
var value = "Hello";
await Assert.That(value).IsNotNullOrEmpty();
}

[Test]
public async Task IsNotNullOrWhiteSpace_WithNullString_Fails()
{
string? nullString = null;
await Assert.That(async () => await Assert.That(nullString).IsNotNullOrWhiteSpace())
.Throws<AssertionException>();
}

[Test]
public async Task IsNotNullOrWhiteSpace_WithEmptyString_Fails()
{
var emptyString = "";
await Assert.That(async () => await Assert.That(emptyString).IsNotNullOrWhiteSpace())
.Throws<AssertionException>();
}

[Test]
public async Task IsNotNullOrWhiteSpace_WithWhitespace_Fails()
{
var whitespace = " ";
await Assert.That(async () => await Assert.That(whitespace).IsNotNullOrWhiteSpace())
.Throws<AssertionException>();
}

[Test]
public async Task IsNotNullOrWhiteSpace_WithNonEmptyString_Passes()
{
var value = "Hello";
await Assert.That(value).IsNotNullOrWhiteSpace();
}

[Test]
public async Task IsNotNullOrWhiteSpace_WithStringContainingWhitespace_Passes()
{
var value = "Hello World";
await Assert.That(value).IsNotNullOrWhiteSpace();
}
}
8 changes: 8 additions & 0 deletions TUnit.Assertions/Conditions/BooleanAssertionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ public static partial class BooleanAssertionExtensions
[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to be false")]
public static bool IsFalse(this bool value) => value == false;

[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to be true")]
public static bool IsTrue(this bool? value) => value == true;

[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to be false")]
public static bool IsFalse(this bool? value) => value == false;
}
10 changes: 5 additions & 5 deletions TUnit.Assertions/Conditions/HasDistinctItemsAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ namespace TUnit.Assertions.Conditions;

/// <summary>
/// Asserts that a collection contains only distinct (unique) items.
/// Inherits from CollectionAssertionBase to enable chaining of collection methods.
/// </summary>
public class HasDistinctItemsAssertion<TValue> : Assertion<TValue>
where TValue : System.Collections.IEnumerable
public class HasDistinctItemsAssertion<TItem> : Sources.CollectionAssertionBase<TItem>
{
public HasDistinctItemsAssertion(
AssertionContext<TValue> context)
AssertionContext<IEnumerable<TItem>> context)
: base(context)
{
}

protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TValue> metadata)
protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<IEnumerable<TItem>> metadata)
{
var value = metadata.Value;
var exception = metadata.Exception;
Expand All @@ -30,7 +30,7 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TValue> m
return Task.FromResult(AssertionResult.Failed("collection was null"));
}

var list = value.Cast<object?>().ToList();
var list = value.ToList();
var distinctList = list.Distinct().ToList();

if (list.Count == distinctList.Count)
Expand Down
68 changes: 68 additions & 0 deletions TUnit.Assertions/Conditions/NullAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,71 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TValue> m

protected override string GetExpectation() => $"to not be default({typeof(TValue).Name})";
}

/// <summary>
/// Asserts that a reference type value is equal to the default value (null).
/// For reference types, this is equivalent to IsNull().
/// </summary>
[AssertionExtension("IsDefault")]
public class IsDefaultReferenceAssertion<TValue> : Assertion<TValue> where TValue : class
{
public IsDefaultReferenceAssertion(
AssertionContext<TValue> context)
: base(context)
{
}

protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TValue> metadata)
{
var value = metadata.Value;
var exception = metadata.Exception;

if (exception != null)
{
return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
}

if (EqualityComparer<TValue>.Default.Equals(value!, default!))
{
return Task.FromResult(AssertionResult.Passed);
}

return Task.FromResult(AssertionResult.Failed($"value is {value}"));
}

protected override string GetExpectation() => $"to be default({typeof(TValue).Name})";
}

/// <summary>
/// Asserts that a reference type value is not the default value (not null).
/// For reference types, this is equivalent to IsNotNull().
/// </summary>
[AssertionExtension("IsNotDefault")]
public class IsNotDefaultReferenceAssertion<TValue> : Assertion<TValue> where TValue : class
{
public IsNotDefaultReferenceAssertion(
AssertionContext<TValue> context)
: base(context)
{
}

protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TValue> metadata)
{
var value = metadata.Value;
var exception = metadata.Exception;

if (exception != null)
{
return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
}

if (!EqualityComparer<TValue>.Default.Equals(value!, default!))
{
return Task.FromResult(AssertionResult.Passed);
}

return Task.FromResult(AssertionResult.Failed($"value is default({typeof(TValue).Name})"));
}

protected override string GetExpectation() => $"to not be default({typeof(TValue).Name})";
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace TUnit.Assertions.Conditions;
[AssertionFrom<string>(nameof(string.IsNullOrWhiteSpace), ExpectationMessage = "be null, empty, or whitespace")]
[AssertionFrom<string>(nameof(string.IsNullOrEmpty), ExpectationMessage = "be null or empty")]
[AssertionFrom<string>(nameof(string.IsNullOrEmpty), CustomName = "IsNotNullOrEmpty", NegateLogic = true, ExpectationMessage = "be null or empty")]
[AssertionFrom<string>(nameof(string.IsNullOrWhiteSpace), CustomName = "IsNotNullOrWhiteSpace", NegateLogic = true, ExpectationMessage = "be null, empty, or whitespace")]
public static partial class StringStaticMethodAssertions
{
}
11 changes: 0 additions & 11 deletions TUnit.Assertions/Extensions/AssertionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,17 +355,6 @@ public static StringLengthAssertion HasLength(
// from the collection assertion classes decorated with [AssertionExtension] attributes.
// This eliminates the need for manual overloads with OverloadResolutionPriority workarounds.

/// <summary>
/// Asserts that the collection contains only distinct (unique) items.
/// </summary>
public static HasDistinctItemsAssertion<TValue> HasDistinctItems<TValue>(
this IAssertionSource<TValue> source)
where TValue : IEnumerable
{
source.Context.ExpressionBuilder.Append(".HasDistinctItems()");
return new HasDistinctItemsAssertion<TValue>(source.Context);
}

// Collection equivalence assertions (IsEquivalentTo/IsNotEquivalentTo for IEnumerable<T>)
// are now generated by AssertionExtensionGenerator.
// Use .Using(comparer) to specify a custom equality comparer.
Expand Down
11 changes: 11 additions & 0 deletions TUnit.Assertions/Sources/CollectionAssertionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ public HasSingleItemAssertion<TItem> HasSingleItem()
return new HasSingleItemAssertion<TItem>(Context);
}

/// <summary>
/// Asserts that the collection contains only distinct (unique) items.
/// This instance method enables calling HasDistinctItems with proper type inference.
/// Example: await Assert.That(list).HasDistinctItems();
/// </summary>
public HasDistinctItemsAssertion<TItem> HasDistinctItems()
{
Context.ExpressionBuilder.Append(".HasDistinctItems()");
return new HasDistinctItemsAssertion<TItem>(Context);
}

/// <summary>
/// Asserts that the collection does not contain the specified item.
/// This instance method enables calling DoesNotContain with proper type inference.
Expand Down