-
-
Notifications
You must be signed in to change notification settings - Fork 84
Description
An assertion of equivalence using ImmutableDictionaries gives a KeyNotFoundException instead of a ComparisonFailure
await Assert.That(ImmutableDictionary<string, int>.Empty.Add("Hello", 1))
.IsEquivalentTo(ImmutableDictionary<string, int>.Empty.Add("Hello2", 1));
Produces
System.Collections.Generic.KeyNotFoundException : The given key 'Hello' was not present in the dictionary.
Stack Trace:
at System.Collections.ThrowHelper.ThrowKeyNotFoundException[TKey](TKey key)
at System.Collections.Immutable.ImmutableDictionary`2.get_Item(TKey key)
at System.Collections.Immutable.ImmutableDictionary`2.System.Collections.IDictionary.get_Item(Object key)
/home/wuzzeb/projects/TUnit/TUnit.Assertions/Compare.cs(95,0): at TUnit.Assertions.Compare.CheckEquivalent[TActual,TExpected](TActual actual, TExpected expected, CompareOptions options, String[] memberNames, MemberType memberType, HashSet`1 visited)+MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
/home/wuzzeb/projects/TUnit/TUnit.Assertions/Assertions/Generics/Conditions/EquivalentToExpectedValueAssertCondition.cs(81,0): at TUnit.Assertions.Assertions.Generics.Conditions.EquivalentToExpectedValueAssertCondition`2.GetResult(TActual actualValue, TExpected expectedValue)
/home/wuzzeb/projects/TUnit/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs(47,0): at TUnit.Assertions.AssertConditions.ExpectedValueAssertCondition`2.GetResult(TActual actualValue, Exception exception, AssertionMetadata assertionMetadata)
/home/wuzzeb/projects/TUnit/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs(91,0): at TUnit.Assertions.AssertConditions.BaseAssertCondition`1.GetAssertionResult(TActual actualValue, Exception exception, AssertionMetadata assertionMetadata, String actualExpression)
/home/wuzzeb/projects/TUnit/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs(72,0): at TUnit.Assertions.AssertConditions.BaseAssertCondition`1.GetAssertionResult(Object actualValue, Exception exception, AssertionMetadata assertionMetadata, String actualExpression)
/home/wuzzeb/projects/TUnit/TUnit.Assertions/AssertionBuilders/AssertionBuilder.cs(142,0): at TUnit.Assertions.AssertionBuilders.AssertionBuilder.ProcessAssertionsAsync()
/home/wuzzeb/projects/TUnit/TUnit.Assertions/AssertionBuilders/InvokableValueAssertionBuilder.cs(34,0): at TUnit.Assertions.AssertionBuilders.InvokableValueAssertionBuilder`1.AssertAndGet()
Which points to
TUnit/TUnit.Assertions/Compare.cs
Line 94 in bc068b5
var actualObject = actualDictionary[key]; |
It makes sense, you make a list of all keys in both dictionaries, in this case ["Hello", "Hello2"] and then look them up in both dictionaries and get a key not found. But I was confused for a long time because if you switch to just Dictionaries instead of ImmutableDictionaries, this code doesn't get that exception
await Assert.That(new Dictionary<string, int> {{"Hello", 1}})
.IsEquivalentTo(new Dictionary<string, int> { { "Hello2", 1 } });
This correctly produces
TUnit.Assertions.Exceptions.AssertionException : Expected new Dictionary<string, int> {{"Hello", 1}} to be equivalent to [[Hello2, 1]]
but [Hello] did not match
Expected: null
Received: 1
at Assert.That(new Dictionary<string, int> {{"Hello", 1}}).IsEquivalentTo(new Dictionary<string, int> {...
I finally realized that the non-generic System.Collections.IDictionary
interface on Dictionary returns null instead of key not found, i.e. the following is true
var d = new Dictionary<string, int>();
d.Add("Hello", 1);
System.Collections.IDictionary d2 = d;
await Assert.That(() => d["Hello2"]).Throws<KeyNotFoundException>();
await Assert.That(d2["Hello2"]).IsNull();
which is why the code in Compare.cs works for Dictionaries because it is converting to the non-generic IDictionary interface before referencing the keys.
But, this means we can make two dictionaries seem to be equivalent if we put in nulls. The following code passes...
await Assert.That(new Dictionary<string, string?> {{"Hello", "a"}, {"Hello2", null}})
.IsEquivalentTo(new Dictionary<string, string?> { { "Hello", "a" } });
because when the "Hello2" key is looked up in the expected dictionary, it gets null which matches.
At first I thought using TryGetValue instead, but that doesn't exist on the non-generic IDictionary. You could use Contains to first check the key, but I think something like the following can give better error messages
var actualKeys = new HashSet<object>(actualDictionary.Keys)
foreach (var expectedKV in expectedDictionary) {
if (actualKeys.Contains(expectedKV.Key)) {
check actualDictionary[expectedKV.Key] with expectedKV.Value
actualKeys.Remove(expectedKV.Key)
} else {
error that expectedKV.Key was expected to exist but does not
}
}
if (actualKeys.Count > 0) {
error that extra keys beyond expected existed
}