I'm using FluentAssertions with ShouldBeEquivalentTo
to compare two dictionaries of type Dictionary<string, string>
but want to exclude one or more specific KeyValue pairs (because they contain timestamps in this case). How to do this?
I tried things like: opt => opt.Excluding(x => x.Single(kv => kv.Key == "MySearchKey"))
but this results in errors like: Message: System.ArgumentException : Expression <Convert(x.Single(kv => (kv.Key == "MySearchKey")))> cannot be used to select a member.
Is what I want possible? Or should I maybe exclude the value only and not the pair (that's maybe even better because the existence of the key will be checked then)? Thanks!
Excluding()
is meant to exclude members of a type, not excluding a member of a collection, see documentation for more info.
Note: the code below is for the current stable version 4.19.4 of Fluent Assertions.
Example:
You want to compare instances of Person
and PersonDTO
, but Person
contains the AnotherProperty
which you want to exclude from the object comparison.
var person = new Person
{
FirstName = "John",
LastName = "McClane",
AnotherProperty = 42
};
var personDTO = new PersonDTO
{
FirstName = "John",
LastName = "McClane"
};
This is where you would use Exclude
to exclude a member of a type.
person.ShouldBeEquivalentTo(personDTO, options => options.Excluding(e => e.AnotherProperty));
In your concrete case I would not use ShouldBeEquivalentTo
.
Consider these two dictionary instances, where you want to omit a member of a collection, here the member with Key == "unknown"
.
var actual = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
["three"] = 3,
["unknown"] = -1,
["fail"] = -2
};
var expected = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
["three"] = 3
};
You could either just filter out the unwanted key-value pairs:
IEnumerable<KeyValuePair<string, int>> filtered = actual.Where(e => e.Key != "unknown");
Now the assertion will be between two IEnumerable<KeyValuePair<string, int>>
s
filtered.Should().Equal(expected);
which will give the following assertion failure message:
FluentAssertions.Execution.AssertionFailedException: 'Expected collection to be equal to {[one, 1], [two, 2], [three, 3]}, but {[one, 1], [two, 2], [three, 3], [fail, -2]} contains 1 item(s) too many.'
Otherwise turn the filtered enumerable back into a dictionary:
Dictionary<string, int> filteredDict = actual.Where(e => e.Key != "unknown")
.ToDictionary(e => e.Key, e => e.Value);
You will now be comparing Dictionary<string, int>
s again:
filteredDict.Should().Equal(expected);
which will give the following assertion failure message:
FluentAssertions.Execution.AssertionFailedException: 'Expected dictionary to be equal to {[one, 1], [two, 2], [three, 3]}, but found additional keys {"fail"}.'
If want to use the second approach and you do this often, you could create extension methods to extract the logic of removing a member from the test method.
public static class DictionaryExtensions
{
public static IDictionary<TKey, TValue> ExceptKeys<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, params TKey[] keys)
{
if (dictionary == null) throw new ArgumentNullException(nameof(dictionary));
if (keys == null) throw new ArgumentNullException(nameof(keys));
return dictionary.Where(e => !keys.Contains(e.Key)).ToDictionary(e => e.Key, e => e.Value);
}
public static IDictionary<TKey, TValue> ExceptValues<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, params TValue[] values)
{
if (dictionary == null) throw new ArgumentNullException(nameof(dictionary));
if (values == null) throw new ArgumentNullException(nameof(values));
return dictionary.Where(e => !values.Contains(e.Value)).ToDictionary(e => e.Key, e => e.Value);
}
}
You can now write an in my opinion more clear and concise test:
actual.ExceptKeys("unknown").Should().Equal(expected);