Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
22 changes: 13 additions & 9 deletions TUnit.Assertions.Tests/Bugs/Tests2117.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,21 @@ at Assert.That(a).IsEquivalentTo(b, collectionOrdering.Value)
[Arguments(new[] { 1, 2, 3 }, new[] { 1, 2, 3, 4 }, null,
"""
Expected a to be equivalent to [1, 2, 3, 4]

but it is [1, 2, 3]


but [3] did not match
Expected: 4
Received: null

at Assert.That(a).IsEquivalentTo(b)
""")]
[Arguments(new[] { 1, 2, 3 }, new[] { 3, 2, 1 }, null,
"""
Expected a to be equivalent to [3, 2, 1]

but it is [1, 2, 3]


but [0] did not match
Expected: 3
Received: 1

at Assert.That(a).IsEquivalentTo(b)
""")]
public async Task IsEquivalent_Fail(int[] a, int[] b, CollectionOrdering? collectionOrdering, string expectedError)
Expand Down Expand Up @@ -69,9 +73,9 @@ at Assert.That(a).IsNotEquivalentTo(b, collectionOrdering.Value)
[Arguments(new[] { 1, 2, 3 }, new[] { 1, 2, 3 }, null,
"""
Expected a to not be equivalent to [1, 2, 3]

but the two Enumerables were equivalent

but it is
at Assert.That(a).IsNotEquivalentTo(b)
""")]
public async Task IsNotEquivalent_Fail(int[] a, int[] b, CollectionOrdering? collectionOrdering, string expectedError)
Expand Down
20 changes: 19 additions & 1 deletion TUnit.Assertions.UnitTests/EquivalentAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ public async Task Different_Enumerables_Are_Equivalent2()
await TUnitAssert.That(array).IsEquivalentTo(list);
}

[Test]
public async Task Different_Dictionaries_Are_Equivalent_With_Different_Ordered_Keys()
{
var dict1 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "A", "A" },
{ "B", "B" },
};

var dict2 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "B", "B" },
{ "A", "A" },
};

await TUnitAssert.That(dict1).IsEquivalentTo(dict2);
}

[Test]
public async Task Different_Enumerables_Are_Equivalent_Any_Order()
{
Expand Down Expand Up @@ -356,7 +374,7 @@ public void Objects_With_Nested_Enumerable_Mismatch_Are_Not_Equivalent()
"""
Expected object1 to be equivalent to object2

but EnumerableItem MyClass.Inner.Inner.Collection.[3] did not match
but MyClass.Inner.Inner.Collection.[3] did not match
Expected: "4"
Received: null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,23 @@ protected override ValueTask<AssertionResult> GetResult(TActual? actualValue, TE
"it is not null");
}

if (actualValue is IEnumerable actualEnumerable && ExpectedValue is IEnumerable expectedEnumerable)
{
var collectionEquivalentToEqualityComparer = new CollectionEquivalentToEqualityComparer<object?>(
new CompareOptions
{
MembersToIgnore = [.._ignoredMembers],
EquivalencyKind = EquivalencyKind,
});

var castedActual = actualEnumerable.Cast<object?>().ToArray();

return AssertionResult
.FailIf(!castedActual.SequenceEqual(expectedEnumerable.Cast<object?>(),
collectionEquivalentToEqualityComparer),
$"{GetFailureMessage(castedActual, collectionEquivalentToEqualityComparer)}");
}
// if (actualValue is IEnumerable actualEnumerable && ExpectedValue is IEnumerable expectedEnumerable
// && actualValue is not IDictionary && ExpectedValue is not IDictionary)
// {
// var collectionEquivalentToEqualityComparer = new CollectionEquivalentToEqualityComparer<object?>(
// new CompareOptions
// {
// MembersToIgnore = [.._ignoredMembers],
// EquivalencyKind = EquivalencyKind,
// });
//
// var castedActual = actualEnumerable.Cast<object?>().ToArray();
//
// return AssertionResult
// .FailIf(!castedActual.SequenceEqual(expectedEnumerable.Cast<object?>(),
// collectionEquivalentToEqualityComparer),
// $"{GetFailureMessage(castedActual, collectionEquivalentToEqualityComparer)}");
// }

bool? isEqual = null;

Expand All @@ -70,13 +71,6 @@ protected override ValueTask<AssertionResult> GetResult(TActual? actualValue, TE
{
isEqual = expectedBasicEqualityComparer.Equals(actualValue, ExpectedValue);
}
else if (actualValue is IEnumerable enumerable && ExpectedValue is IEnumerable enumerable2)
{
IEnumerable<object> castedEnumerable = [..enumerable];
IEnumerable<object> castedEnumerable2 = [..enumerable2];

isEqual = castedEnumerable.SequenceEqual(castedEnumerable2);
}
if (isEqual != null)
{
return AssertionResult
Expand All @@ -96,6 +90,15 @@ protected override ValueTask<AssertionResult> GetResult(TActual? actualValue, TE
{
return FailWithMessage(Formatter.Format(firstFailure.Actual));
}

if (firstFailure.Type == MemberType.EnumerableItem)
{
return FailWithMessage($"""
{string.Join(".", firstFailure.NestedMemberNames)} did not match
Expected: {Formatter.Format(firstFailure.Expected)}
Received: {Formatter.Format(firstFailure.Actual)}
""");
}

return FailWithMessage($"""
{firstFailure.Type} {string.Join(".", firstFailure.NestedMemberNames)} did not match
Expand Down
15 changes: 1 addition & 14 deletions TUnit.Assertions/Assertions/Throws/ThrowsException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public ThrowsException<TActual, TException> WithMessage(string expected, [Caller
public ThrowsException<TActual, Exception> WithInnerException()
{
_source.AppendExpression($"{nameof(WithInnerException)}()");
return new(_delegateAssertionBuilder, _source, e => _selector(e)?.InnerException);
return new ThrowsException<TActual, Exception>(_delegateAssertionBuilder, _source, e => _selector(e)?.InnerException);
}

public TaskAwaiter<TException?> GetAwaiter()
Expand All @@ -57,18 +57,5 @@ public ThrowsException<TActual, Exception> WithInnerException()

public DelegateOr<object?> Or => _delegateAssertionBuilder.Or;

internal void RegisterAssertion(BaseAssertCondition<TActual> condition, string?[] argumentExpressions, [CallerMemberName] string? caller = null) => _source.RegisterAssertion(condition, argumentExpressions, caller);

internal void RegisterAssertion(Func<Func<Exception?, Exception?>, BaseAssertCondition<TActual>> conditionFunc, string?[] argumentExpressions, [CallerMemberName] string? caller = null) => _source.RegisterAssertion(conditionFunc(_selector), argumentExpressions, caller);

private async ValueTask<AssertionData> AssertionDataTask()
{
var assertionData = await _delegateAssertionBuilder.ProcessAssertionsAsync();

return assertionData with
{
Result = assertionData.Exception as TException,
Exception = null
};
}
}
32 changes: 31 additions & 1 deletion TUnit.Assertions/Compare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ public static IEnumerable<ComparisonFailure> CheckEquivalent<
TExpected expected, CompareOptions options,
int? index)
{
return CheckEquivalent(actual, expected, options, [InitialMemberName<TActual>(actual, index)], MemberType.Value, []);
var initialMemberName = InitialMemberName<TActual>(actual, index);

string[] memberNames = string.IsNullOrEmpty(initialMemberName) ? [] : [initialMemberName];

return CheckEquivalent(actual, expected, options, memberNames, MemberType.Value, []);
}

private static IEnumerable<ComparisonFailure> CheckEquivalent<
Expand Down Expand Up @@ -77,6 +81,27 @@ private static IEnumerable<ComparisonFailure> CheckEquivalent<
{
yield break;
}

if(actual is IDictionary actualDictionary && expected is IDictionary expectedDictionary)
{
var keys = actualDictionary.Keys.Cast<object>()
.Concat(expectedDictionary.Keys.Cast<object>())
.Distinct()
.ToArray();

foreach (var key in keys)
{
var actualObject = actualDictionary[key];
var expectedObject = expectedDictionary[key];

foreach (var comparisonFailure in CheckEquivalent(actualObject, expectedObject, options,
[..memberNames, $"[{key}]"], MemberType.EnumerableItem, visited))
{
yield return comparisonFailure;
}
}
yield break;
}

if (actual is IEnumerable actualEnumerable && expected is IEnumerable expectedEnumerable)
{
Expand Down Expand Up @@ -206,6 +231,11 @@ private static IEnumerable<ComparisonFailure> CheckEquivalent<

private static string InitialMemberName<TActual>(object? actual, int? index)
{
if (actual is IEnumerable)
{
return string.Empty;
}

var type = actual?.GetType().Name ?? typeof(TActual).Name;

if (index is null)
Expand Down
3 changes: 2 additions & 1 deletion TUnit.Assertions/MemberType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ public enum MemberType
Property,
Field,
Value,
EnumerableItem
EnumerableItem,
DictionaryItem
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ namespace TUnit.Assertions
Field = 1,
Value = 2,
EnumerableItem = 3,
DictionaryItem = 4,
}
}
namespace TUnit.Assertions.AssertConditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ namespace TUnit.Assertions
Field = 1,
Value = 2,
EnumerableItem = 3,
DictionaryItem = 4,
}
}
namespace TUnit.Assertions.AssertConditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ namespace TUnit.Assertions
Field = 1,
Value = 2,
EnumerableItem = 3,
DictionaryItem = 4,
}
}
namespace TUnit.Assertions.AssertConditions
Expand Down
Loading