diff --git a/TUnit.Assertions.Tests/Bugs/Tests2117.cs b/TUnit.Assertions.Tests/Bugs/Tests2117.cs index c5a4ee77c1..347a1e9947 100644 --- a/TUnit.Assertions.Tests/Bugs/Tests2117.cs +++ b/TUnit.Assertions.Tests/Bugs/Tests2117.cs @@ -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) @@ -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) diff --git a/TUnit.Assertions.UnitTests/EquivalentAssertionTests.cs b/TUnit.Assertions.UnitTests/EquivalentAssertionTests.cs index e04e822fbe..761eb7aedf 100644 --- a/TUnit.Assertions.UnitTests/EquivalentAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/EquivalentAssertionTests.cs @@ -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(StringComparer.OrdinalIgnoreCase) + { + { "A", "A" }, + { "B", "B" }, + }; + + var dict2 = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "B", "B" }, + { "A", "A" }, + }; + + await TUnitAssert.That(dict1).IsEquivalentTo(dict2); + } + [Test] public async Task Different_Enumerables_Are_Equivalent_Any_Order() { @@ -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 diff --git a/TUnit.Assertions/Assertions/Generics/Conditions/EquivalentToExpectedValueAssertCondition.cs b/TUnit.Assertions/Assertions/Generics/Conditions/EquivalentToExpectedValueAssertCondition.cs index da6fea5622..cb4100126f 100644 --- a/TUnit.Assertions/Assertions/Generics/Conditions/EquivalentToExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/Assertions/Generics/Conditions/EquivalentToExpectedValueAssertCondition.cs @@ -43,22 +43,23 @@ protected override ValueTask GetResult(TActual? actualValue, TE "it is not null"); } - if (actualValue is IEnumerable actualEnumerable && ExpectedValue is IEnumerable expectedEnumerable) - { - var collectionEquivalentToEqualityComparer = new CollectionEquivalentToEqualityComparer( - new CompareOptions - { - MembersToIgnore = [.._ignoredMembers], - EquivalencyKind = EquivalencyKind, - }); - - var castedActual = actualEnumerable.Cast().ToArray(); - - return AssertionResult - .FailIf(!castedActual.SequenceEqual(expectedEnumerable.Cast(), - 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( + // new CompareOptions + // { + // MembersToIgnore = [.._ignoredMembers], + // EquivalencyKind = EquivalencyKind, + // }); + // + // var castedActual = actualEnumerable.Cast().ToArray(); + // + // return AssertionResult + // .FailIf(!castedActual.SequenceEqual(expectedEnumerable.Cast(), + // collectionEquivalentToEqualityComparer), + // $"{GetFailureMessage(castedActual, collectionEquivalentToEqualityComparer)}"); + // } bool? isEqual = null; @@ -70,13 +71,6 @@ protected override ValueTask GetResult(TActual? actualValue, TE { isEqual = expectedBasicEqualityComparer.Equals(actualValue, ExpectedValue); } - else if (actualValue is IEnumerable enumerable && ExpectedValue is IEnumerable enumerable2) - { - IEnumerable castedEnumerable = [..enumerable]; - IEnumerable castedEnumerable2 = [..enumerable2]; - - isEqual = castedEnumerable.SequenceEqual(castedEnumerable2); - } if (isEqual != null) { return AssertionResult @@ -96,6 +90,15 @@ protected override ValueTask 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 diff --git a/TUnit.Assertions/Assertions/Throws/ThrowsException.cs b/TUnit.Assertions/Assertions/Throws/ThrowsException.cs index f2f6b16b87..dc96749253 100644 --- a/TUnit.Assertions/Assertions/Throws/ThrowsException.cs +++ b/TUnit.Assertions/Assertions/Throws/ThrowsException.cs @@ -38,7 +38,7 @@ public ThrowsException WithMessage(string expected, [Caller public ThrowsException WithInnerException() { _source.AppendExpression($"{nameof(WithInnerException)}()"); - return new(_delegateAssertionBuilder, _source, e => _selector(e)?.InnerException); + return new ThrowsException(_delegateAssertionBuilder, _source, e => _selector(e)?.InnerException); } public TaskAwaiter GetAwaiter() @@ -57,18 +57,5 @@ public ThrowsException WithInnerException() public DelegateOr Or => _delegateAssertionBuilder.Or; - internal void RegisterAssertion(BaseAssertCondition condition, string?[] argumentExpressions, [CallerMemberName] string? caller = null) => _source.RegisterAssertion(condition, argumentExpressions, caller); - internal void RegisterAssertion(Func, BaseAssertCondition> conditionFunc, string?[] argumentExpressions, [CallerMemberName] string? caller = null) => _source.RegisterAssertion(conditionFunc(_selector), argumentExpressions, caller); - - private async ValueTask AssertionDataTask() - { - var assertionData = await _delegateAssertionBuilder.ProcessAssertionsAsync(); - - return assertionData with - { - Result = assertionData.Exception as TException, - Exception = null - }; - } } \ No newline at end of file diff --git a/TUnit.Assertions/Compare.cs b/TUnit.Assertions/Compare.cs index 278cfab54b..4396b0fe43 100644 --- a/TUnit.Assertions/Compare.cs +++ b/TUnit.Assertions/Compare.cs @@ -26,7 +26,11 @@ public static IEnumerable CheckEquivalent< TExpected expected, CompareOptions options, int? index) { - return CheckEquivalent(actual, expected, options, [InitialMemberName(actual, index)], MemberType.Value, []); + var initialMemberName = InitialMemberName(actual, index); + + string[] memberNames = string.IsNullOrEmpty(initialMemberName) ? [] : [initialMemberName]; + + return CheckEquivalent(actual, expected, options, memberNames, MemberType.Value, []); } private static IEnumerable CheckEquivalent< @@ -77,6 +81,27 @@ private static IEnumerable CheckEquivalent< { yield break; } + + if(actual is IDictionary actualDictionary && expected is IDictionary expectedDictionary) + { + var keys = actualDictionary.Keys.Cast() + .Concat(expectedDictionary.Keys.Cast()) + .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) { @@ -206,6 +231,11 @@ private static IEnumerable CheckEquivalent< private static string InitialMemberName(object? actual, int? index) { + if (actual is IEnumerable) + { + return string.Empty; + } + var type = actual?.GetType().Name ?? typeof(TActual).Name; if (index is null) diff --git a/TUnit.Assertions/MemberType.cs b/TUnit.Assertions/MemberType.cs index 845b5f9ab7..5148336059 100644 --- a/TUnit.Assertions/MemberType.cs +++ b/TUnit.Assertions/MemberType.cs @@ -5,5 +5,6 @@ public enum MemberType Property, Field, Value, - EnumerableItem + EnumerableItem, + DictionaryItem } \ No newline at end of file diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt index 57e3cdf3fa..11aab3b441 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt @@ -98,6 +98,7 @@ namespace TUnit.Assertions Field = 1, Value = 2, EnumerableItem = 3, + DictionaryItem = 4, } } namespace TUnit.Assertions.AssertConditions 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 7754daa620..f12e66fb7e 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 @@ -106,6 +106,7 @@ namespace TUnit.Assertions Field = 1, Value = 2, EnumerableItem = 3, + DictionaryItem = 4, } } namespace TUnit.Assertions.AssertConditions 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 fb242b5c03..89c6f0ed9e 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 @@ -106,6 +106,7 @@ namespace TUnit.Assertions Field = 1, Value = 2, EnumerableItem = 3, + DictionaryItem = 4, } } namespace TUnit.Assertions.AssertConditions