-
-
Notifications
You must be signed in to change notification settings - Fork 109
feat: add custom predicate support for equivalency assertions using FuncEqualityComparer #4141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Pull Request ReviewSummaryThis PR successfully implements custom equality predicate support for equivalency assertions by introducing a new ✅ Positive Aspects
🔍 Observations & Suggestions1. Performance Consideration - GetHashCode Implementation (TUnit.Assertions/Conditions/Helpers/FuncEqualityComparer.cs:24)The constant Suggestion: Add an XML documentation warning about performance implications: /// <summary>
/// An IEqualityComparer implementation that uses a custom Func for equality comparison.
/// This allows users to pass lambda predicates to assertion methods like Using().
/// </summary>
/// <remarks>
/// <para>
/// ⚠️ PERFORMANCE WARNING: This comparer always returns a constant hash code,
/// which forces O(n²) comparisons in collection equivalency checks.
/// For large collections (>1000 elements), consider implementing IEqualityComparer{T} directly.
/// </para>
/// </remarks>2. Test Quality (TUnit.Assertions.Tests/Bugs/Tests1600.cs:32)The test at line 32 uses Suggestion: Consider a more meaningful test: [Test]
public async Task Custom_Predicate_Always_True()
{
MyModel[] array1 = [new(), new(), new()];
MyModel[] array2 = [new(), new(), new()];
// Verify that even with different IDs, the predicate makes them equivalent
await Assert.That(array1).IsEquivalentTo(array2).Using((x, y) => true);
// Add assertion to verify IDs are actually different
await Assert.That(array1[0].Id).IsNotEqualTo(array2[0].Id);
}3. Missing Edge Case TestsConsider adding tests for:
Example: [Test]
public async Task Custom_Predicate_With_Null_Elements()
{
User?[] array1 = [new User("Alice", 30), null];
User?[] array2 = [null, new User("Alice", 30)];
await Assert.That(array1)
.IsEquivalentTo(array2)
.Using((u1, u2) => u1?.Name == u2?.Name && u1?.Age == u2?.Age);
}4. Consistency Check (All assertion files)All 5. Minor: Sealed Class (FuncEqualityComparer.cs:8)Good use of 🎯 Adherence to TUnit Guidelines (from CLAUDE.md)✅ Code Style: Uses modern C# syntax (collection expressions, nullable reference types, file-scoped namespaces)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds custom predicate support for equivalency assertions by introducing a FuncEqualityComparer<T> helper class. This enhancement allows users to pass lambda predicates directly to Using() methods instead of having to implement IEqualityComparer<T>, significantly improving the API's usability for quick comparisons.
Key Changes
- Introduced
FuncEqualityComparer<T>that wrapsFunc<T?, T?, bool>predicates intoIEqualityComparer<T>implementations - Added
Using(Func<T?, T?, bool>)overloads to four assertion types:IsEquivalentToAssertion,NotEquivalentToAssertion,IsEquatableOrEqualToAssertion, andDictionaryContainsKeyAssertion - Added comprehensive tests demonstrating usage with lambda predicates for collection equivalency checks
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| TUnit.Assertions/Conditions/Helpers/FuncEqualityComparer.cs | New internal helper class that adapts Func predicates to IEqualityComparer interface with proper GetHashCode handling for custom comparers |
| TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs | Added Using(Func) overload to support lambda predicates for collection equivalency assertions |
| TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs | Added Using(Func) overload to support lambda predicates for negative collection equivalency assertions |
| TUnit.Assertions/Conditions/PredicateAssertions.cs | Added Using(Func) overload to IsEquatableOrEqualToAssertion for custom equality predicates |
| TUnit.Assertions/Conditions/DictionaryAssertions.cs | Added Using(Func) overload to DictionaryContainsKeyAssertion for custom key comparison predicates |
| TUnit.Assertions.Tests/Bugs/Tests1600.cs | Added test cases demonstrating predicate-based assertions with lambda expressions and property comparisons |
| TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.*.verified.txt | Updated API snapshots to reflect the new public Using(Func) overloads across all target frameworks |
| [Test] | ||
| public async Task Custom_Predicate() | ||
| { | ||
| MyModel[] array1 = [new(), new(), new()]; | ||
| MyModel[] array2 = [new(), new(), new()]; | ||
|
|
||
| // Using a lambda predicate instead of implementing IEqualityComparer | ||
| await Assert.That(array1).IsEquivalentTo(array2).Using((x, y) => true); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Custom_Predicate_With_Property_Comparison() | ||
| { | ||
| var users1 = new[] { new User("Alice", 30), new User("Bob", 25) }; | ||
| var users2 = new[] { new User("Bob", 25), new User("Alice", 30) }; | ||
|
|
||
| // Elements have different order but are equivalent by name and age | ||
| await Assert.That(users1) | ||
| .IsEquivalentTo(users2) | ||
| .Using((u1, u2) => u1?.Name == u2?.Name && u1?.Age == u2?.Age); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Custom_Predicate_Not_Equivalent() | ||
| { | ||
| var users1 = new[] { new User("Alice", 30), new User("Bob", 25) }; | ||
| var users2 = new[] { new User("Charlie", 35), new User("Diana", 28) }; | ||
|
|
||
| await Assert.That(users1) | ||
| .IsNotEquivalentTo(users2) | ||
| .Using((u1, u2) => u1?.Name == u2?.Name && u1?.Age == u2?.Age); | ||
| } |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test coverage for the new predicate overloads is incomplete. While IsEquivalentTo and NotEquivalentTo assertions are well tested, there are no tests for the other two assertion types that received the same Using(Func) overload:
- IsEquatableOrEqualToAssertion (in PredicateAssertions.cs)
- DictionaryContainsKeyAssertion (in DictionaryAssertions.cs)
Consider adding test cases similar to these existing ones to verify that the predicate overload works correctly for IsEquatableOrEqualTo and ContainsKey assertions. This would ensure complete coverage of the new API surface.
Fixes #4112