Skip to content

Commit f8cd97f

Browse files
authored
Improve exception message for IsEquivalentTo when using enumerables without CollectionOrdering enum overload (thomhurst#2120)
* Improve exception message for IsEquivalentTo when using enumerables without CollectionOrdering enum overload * Update snaps
1 parent be39bbc commit f8cd97f

18 files changed

+257
-50
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System.Collections.Immutable;
2+
using TUnit.Assertions.AssertConditions.Throws;
3+
using TUnit.Assertions.Enums;
4+
5+
namespace TUnit.Assertions.Tests.Bugs;
6+
7+
public class Tests2117
8+
{
9+
[Test]
10+
[Arguments(new[] { 1, 2, 3 }, new[] { 3, 2, 1 }, CollectionOrdering.Matching,
11+
"""
12+
Expected a to be equivalent to [3, 2, 1]
13+
14+
but it is [1, 2, 3]
15+
16+
at Assert.That(a).IsEquivalentTo(b, collectionOrdering.Value)
17+
""")]
18+
[Arguments(new[] { 1, 2, 3 }, new[] { 1, 2, 3, 4 }, CollectionOrdering.Any,
19+
"""
20+
Expected a to be equivalent to [1, 2, 3, 4]
21+
22+
but it is [1, 2, 3]
23+
24+
at Assert.That(a).IsEquivalentTo(b, collectionOrdering.Value)
25+
""")]
26+
[Arguments(new[] { 1, 2, 3 }, new[] { 1, 2, 3, 4 }, null,
27+
"""
28+
Expected a to be equivalent to [1, 2, 3, 4]
29+
30+
but it is [1, 2, 3]
31+
32+
at Assert.That(a).IsEquivalentTo(b)
33+
""")]
34+
[Arguments(new[] { 1, 2, 3 }, new[] { 3, 2, 1 }, null,
35+
"""
36+
Expected a to be equivalent to [3, 2, 1]
37+
38+
but it is [1, 2, 3]
39+
40+
at Assert.That(a).IsEquivalentTo(b)
41+
""")]
42+
public async Task IsEquivalent_Fail(int[] a, int[] b, CollectionOrdering? collectionOrdering, string expectedError)
43+
{
44+
await Assert.That(async () =>
45+
await (collectionOrdering is null
46+
? Assert.That(a).IsEquivalentTo(b)
47+
: Assert.That(a).IsEquivalentTo(b, collectionOrdering.Value))
48+
).Throws<AssertionException>()
49+
.WithMessage(expectedError);
50+
}
51+
52+
[Test]
53+
[Arguments(new[] { 1, 2, 3 }, new[] { 3, 2, 1 }, CollectionOrdering.Any,
54+
"""
55+
Expected a to not be equivalent to [3, 2, 1]
56+
57+
but the two Enumerables were equivalent
58+
59+
at Assert.That(a).IsNotEquivalentTo(b, collectionOrdering.Value)
60+
""")]
61+
[Arguments(new[] { 1, 2, 3 }, new[] { 1, 2, 3 }, CollectionOrdering.Matching,
62+
"""
63+
Expected a to not be equivalent to [1, 2, 3]
64+
65+
but the two Enumerables were equivalent
66+
67+
at Assert.That(a).IsNotEquivalentTo(b, collectionOrdering.Value)
68+
""")]
69+
[Arguments(new[] { 1, 2, 3 }, new[] { 1, 2, 3 }, null,
70+
"""
71+
Expected a to not be equivalent to [1, 2, 3]
72+
73+
but the two Enumerables were equivalent
74+
75+
at Assert.That(a).IsNotEquivalentTo(b)
76+
""")]
77+
public async Task IsNotEquivalent_Fail(int[] a, int[] b, CollectionOrdering? collectionOrdering, string expectedError)
78+
{
79+
await Assert.That(async () =>
80+
await (collectionOrdering is null
81+
? Assert.That(a).IsNotEquivalentTo(b)
82+
: Assert.That(a).IsNotEquivalentTo(b, collectionOrdering.Value))
83+
).Throws<AssertionException>()
84+
.WithMessage(expectedError);
85+
}
86+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace TUnit.Assertions.Tests;
2+
3+
public class GlobalSetup
4+
{
5+
[Before(TestSession)]
6+
public static void DisableTruncation()
7+
{
8+
Environment.SetEnvironmentVariable("TUNIT_ASSERTIONS_DISABLE_TRUNCATION", "true");
9+
}
10+
}

TUnit.Assertions.UnitTests/EquivalentAssertionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void Different_Enumerables__Thrown_When_Non_Matching_Order()
8888
8989
but it is [1, 5, 2, 3, 4]
9090
91-
at Assert.That(array).IsEquivalentTo(list)
91+
at Assert.That(array).IsEquivalentTo(list, CollectionOrdering.Matching)
9292
"""
9393
)); }
9494

@@ -107,7 +107,7 @@ public void Different_Enumerables__Thrown_When_Non_Matching_Order2()
107107
108108
but it is [1, 5, 2, 3, 4]
109109
110-
at Assert.That(array).IsEquivalentTo(list)
110+
at Assert.That(array).IsEquivalentTo(list, CollectionOrdering.Matching)
111111
"""
112112
));
113113
}

TUnit.Assertions/AssertConditions/BaseAssertCondition.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public AssertionResult FailWithMessage(string message)
3232

3333
protected abstract string GetExpectation();
3434

35+
internal string Expectation => GetExpectation();
36+
3537
internal virtual string GetExpectationWithReason()
3638
=> $"{GetExpectation()}{GetBecauseReason()}";
3739

TUnit.Assertions/AssertConditions/Interfaces/ConvertedValueSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace TUnit.Assertions.AssertConditions.Interfaces;
88
public class ConvertedValueSource<TFromType, TToType>(IValueSource<TFromType> source, ConvertToAssertCondition<TFromType, TToType> convertToAssertCondition) : IValueSource<TToType?>
99
{
1010
public string? ActualExpression { get; } = source.ActualExpression;
11-
public Stack<BaseAssertCondition> Assertions { get; } = new([new NoOpAssertionCondition<TToType>()]);
11+
public Stack<BaseAssertCondition> Assertions { get; } = new([new NoOpAssertionCondition<TToType>(convertToAssertCondition.Expectation)]);
1212
public ValueTask<AssertionData> AssertionDataTask { get; } = ConvertAsync(source, convertToAssertCondition);
1313

1414
public StringBuilder ExpressionBuilder { get; } = source.ExpressionBuilder;

TUnit.Assertions/AssertionBuilders/AndConvertedTypeAssertionBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public abstract class ConvertedDelegateAssertionBuilder<TToType> : AssertionBuil
1515
// Due to the new generic constraint, so we need to populate the stack with something
1616
protected ConvertedDelegateAssertionBuilder(ISource source,
1717
ValueTask<AssertionData> mappedData)
18-
: base(mappedData, source.ActualExpression!, source.ExpressionBuilder, new Stack<BaseAssertCondition>([new NoOpAssertionCondition<TToType>()]))
18+
: base(mappedData, source.ActualExpression!, source.ExpressionBuilder, new Stack<BaseAssertCondition>([new NoOpAssertionCondition<TToType>(source.Assertions.Peek().Expectation)]))
1919
{
2020
OtherTypeAssertionBuilder = source as IInvokableAssertionBuilder;
2121
}

TUnit.Assertions/Assertions/Collections/CollectionsIsExtensions.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,27 @@ public static InvokableValueAssertionBuilder<TActual> IsEquivalentTo<
2626
TActual,
2727
TInner>(this IValueSource<TActual> valueSource,
2828
IEnumerable<TInner> expected, IEqualityComparer<TInner> comparer,
29-
[CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
29+
[CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null,
30+
[CallerArgumentExpression(nameof(comparer))] string doNotPopulateThisValue2 = null)
3031
where TActual : IEnumerable<TInner>
3132
{
32-
return IsEquivalentTo(valueSource, expected, comparer, CollectionOrdering.Matching, doNotPopulateThisValue);
33+
return IsEquivalentTo(valueSource, expected, comparer, CollectionOrdering.Matching, doNotPopulateThisValue, doNotPopulateThisValue2);
3334
}
3435

3536
public static InvokableValueAssertionBuilder<TActual> IsEquivalentTo<TActual,
3637
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
37-
TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
38+
TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null, [CallerArgumentExpression(nameof(collectionOrdering))] string doNotPopulateThisValue2 = null)
3839
where TActual : IEnumerable<TInner>
3940
{
40-
return IsEquivalentTo(valueSource, expected, new CollectionEquivalentToEqualityComparer<TInner>(), collectionOrdering, doNotPopulateThisValue);
41+
return IsEquivalentTo(valueSource, expected, new CollectionEquivalentToEqualityComparer<TInner>(), collectionOrdering, doNotPopulateThisValue, doNotPopulateThisValue2);
4142
}
4243

43-
public static InvokableValueAssertionBuilder<TActual> IsEquivalentTo<TActual, TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, IEqualityComparer<TInner> comparer, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
44+
public static InvokableValueAssertionBuilder<TActual> IsEquivalentTo<TActual, TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, IEqualityComparer<TInner> comparer, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null, [CallerArgumentExpression(nameof(collectionOrdering))] string doNotPopulateThisValue2 = null)
4445
where TActual : IEnumerable<TInner>
4546
{
4647
return valueSource.RegisterAssertion(
4748
new EnumerableEquivalentToExpectedValueAssertCondition<TActual, TInner>(expected,
48-
comparer, collectionOrdering), [doNotPopulateThisValue]);
49+
comparer, collectionOrdering), [doNotPopulateThisValue, doNotPopulateThisValue2]);
4950
}
5051

5152
public static InvokableValueAssertionBuilder<IEnumerable<TInner>> IsInOrder<TInner>(

TUnit.Assertions/Assertions/Collections/CollectionsIsNotExtensions.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,50 @@
11
#nullable disable
22

3+
using System.Diagnostics.CodeAnalysis;
34
using System.Runtime.CompilerServices;
45
using TUnit.Assertions.AssertConditions.Collections;
56
using TUnit.Assertions.AssertConditions.Interfaces;
67
using TUnit.Assertions.AssertionBuilders;
8+
using TUnit.Assertions.Enums;
9+
using TUnit.Assertions.Equality;
710

811
namespace TUnit.Assertions.Extensions;
912

1013
public static class CollectionsIsNotExtensions
1114
{
12-
public static InvokableValueAssertionBuilder<TActual> IsNotEquivalentTo<TActual, TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, IEqualityComparer<TInner> equalityComparer = null, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
15+
public static InvokableValueAssertionBuilder<TActual> IsNotEquivalentTo<TActual,
16+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
17+
TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
1318
where TActual : IEnumerable<TInner>
1419
{
15-
return valueSource.RegisterAssertion(new EnumerableNotEquivalentToExpectedValueAssertCondition<TActual, TInner>(expected, equalityComparer)
16-
, [doNotPopulateThisValue]);
20+
return IsNotEquivalentTo(valueSource, expected, new CollectionEquivalentToEqualityComparer<TInner>(), doNotPopulateThisValue, null);
21+
}
22+
23+
public static InvokableValueAssertionBuilder<TActual> IsNotEquivalentTo<
24+
TActual,
25+
TInner>(this IValueSource<TActual> valueSource,
26+
IEnumerable<TInner> expected, IEqualityComparer<TInner> comparer,
27+
[CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null,
28+
[CallerArgumentExpression(nameof(comparer))] string doNotPopulateThisValue2 = null)
29+
where TActual : IEnumerable<TInner>
30+
{
31+
return IsNotEquivalentTo(valueSource, expected, comparer, CollectionOrdering.Matching, doNotPopulateThisValue, doNotPopulateThisValue2);
32+
}
33+
34+
public static InvokableValueAssertionBuilder<TActual> IsNotEquivalentTo<TActual,
35+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
36+
TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null, [CallerArgumentExpression(nameof(collectionOrdering))] string doNotPopulateThisValue2 = null)
37+
where TActual : IEnumerable<TInner>
38+
{
39+
return IsNotEquivalentTo(valueSource, expected, new CollectionEquivalentToEqualityComparer<TInner>(), collectionOrdering, doNotPopulateThisValue, doNotPopulateThisValue2);
40+
}
41+
42+
public static InvokableValueAssertionBuilder<TActual> IsNotEquivalentTo<TActual, TInner>(this IValueSource<TActual> valueSource, IEnumerable<TInner> expected, IEqualityComparer<TInner> comparer, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null, [CallerArgumentExpression(nameof(collectionOrdering))] string doNotPopulateThisValue2 = null)
43+
where TActual : IEnumerable<TInner>
44+
{
45+
return valueSource.RegisterAssertion(
46+
new EnumerableNotEquivalentToExpectedValueAssertCondition<TActual, TInner>(expected,
47+
comparer, collectionOrdering), [doNotPopulateThisValue, doNotPopulateThisValue2]);
1748
}
1849

1950
public static InvokableValueAssertionBuilder<IEnumerable<TInner>> IsNotEmpty<TInner>(this IValueSource<IEnumerable<TInner>> valueSource)

TUnit.Assertions/Assertions/Collections/Conditions/EnumerableEquivalentToExpectedValueAssertCondition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace TUnit.Assertions.AssertConditions.Collections;
77

88
public class EnumerableEquivalentToExpectedValueAssertCondition<TActual, TInner>(
9-
IEnumerable<TInner> expected,
9+
IEnumerable<TInner>? expected,
1010
IEqualityComparer<TInner?> equalityComparer,
1111
CollectionOrdering collectionOrdering)
1212
: ExpectedValueAssertCondition<TActual, IEnumerable<TInner>>(expected)
Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
using TUnit.Assertions.Enums;
2+
using TUnit.Assertions.Helpers;
3+
14
namespace TUnit.Assertions.AssertConditions.Collections;
25

36
public class EnumerableNotEquivalentToExpectedValueAssertCondition<TActual, TInner>(
4-
IEnumerable<TInner> expected,
5-
IEqualityComparer<TInner?>? equalityComparer)
7+
IEnumerable<TInner>? expected,
8+
IEqualityComparer<TInner?> equalityComparer,
9+
CollectionOrdering collectionOrdering)
610
: ExpectedValueAssertCondition<TActual, IEnumerable<TInner>>(expected)
711
where TActual : IEnumerable<TInner>?
812
{
9-
protected override string GetExpectation() => $" to be not equivalent to {(expected != null ? string.Join(",", expected) : null)}";
13+
protected override string GetExpectation() => $"to not be equivalent to {(expected != null ? Formatter.Format(expected) : null)}";
1014

1115
protected override ValueTask<AssertionResult> GetResult(TActual? actualValue, IEnumerable<TInner>? expectedValue)
1216
{
@@ -15,11 +19,19 @@ protected override ValueTask<AssertionResult> GetResult(TActual? actualValue, IE
1519
return AssertionResult.Passed;
1620
}
1721

22+
var enumeratedActual = actualValue?.ToArray();
23+
var enumeratedExpected = expectedValue?.ToArray();
24+
1825
return AssertionResult
1926
.FailIf(actualValue is null && expectedValue is null,
2027
"it is null")
21-
.OrFailIf(actualValue!.SequenceEqual(expectedValue!, equalityComparer),
22-
"the two Enumerables were equivalent"
23-
);
28+
.OrFailIf(collectionOrdering == CollectionOrdering.Matching && enumeratedActual!.SequenceEqual(enumeratedExpected!, equalityComparer), "the two Enumerables were equivalent")
29+
.OrFailIf(collectionOrdering == CollectionOrdering.Any && EqualsAnyOrder(enumeratedActual!, enumeratedExpected!, equalityComparer), "the two Enumerables were equivalent");
30+
}
31+
32+
private static bool EqualsAnyOrder(TInner[] actualValue, TInner[] expectedValue,
33+
IEqualityComparer<TInner?> equalityComparer)
34+
{
35+
return actualValue.Length == expectedValue.Length && !actualValue.Except(expectedValue, equalityComparer).Any();
2436
}
2537
}

0 commit comments

Comments
 (0)