How to use Exclude in FluentAssertions for property in collection?
C#Unit TestingFluent AssertionsC# Problem Overview
I have two classes:
public class ClassA
{
public int? ID {get; set;}
public IEnumerable<ClassB> Children {get; set;}
}
public class ClassB
{
public int? ID {get; set;}
public string Name {get; set;}
}
I want to use fluent assertions to compare to ClassA instances. However I want to ignore the IDs (because the IDs will have been assigned after the save).
I know I can do this:
expectedA.ShouldBeEquivalentTo(actualA, options => options.Excluding(x => x.PropertyPath == "Children[0].ID"));
Which I can obviously repeat for each ClassB in the collection. However I'm looking for a way to exclude the all the IDs (rather than doing an exclude for each element).
I've read this question however if I remove the [0] indexers the assertions fail.
Is this possible?
C# Solutions
Solution 1 - C#
What about?
expected.ShouldBeEquivalentTo(actualA, options => options.Excluding(su =>
(su.RuntimeType == typeof(ClassB)) && (su.PropertyPath.EndsWith("Id")));`
Or you could do a RegEx match on the property path, such as
expected.ShouldBeEquivalentTo(actualA, options => options.Excluding(su => (Regex.IsMatch
("Children\[.+\]\.ID"));
I actually like that last one, but the regex stuff makes it a bit difficult to read. Maybe I should extend ISubjectInfo
with a method to match the path against a wildcard pattern, so that you can do this:
expected.ShouldBeEquivalentTo(actualA, options => options
.Excluding(su => su.PathMatches("Children[*].ID")));
Solution 2 - C#
I've just come across a similar problem and the latest version of FluentAssertions has changed things a bit.
My objects contains dictionaries of other objects. The objects in the dictionaries contain other objects that I want to exclude. The scenario I have is around testing Json serialization where I ignore certain properties.
This works for me:
gotA.ShouldBeEquivalentTo(expectedB , config =>
config
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType == typeof(Venue))
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType == typeof(Exhibit))
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType == typeof(Content))
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType == typeof(Survey))
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType == typeof(Media))
);
Took some time to work out how to do it, but it's really useful!
Solution 3 - C#
Simple way would be to set assertions on collection directly, combined with its exclusion on ClassA
equivalency assertion:
expectedA.ShouldBeEquivalentTo(expectedB,
o => o.Excluding(s => s.PropertyInfo.Name == "Children"));
expectedA.Children.ShouldBeEquivalentTo(expectedB.Children,
o => o.Excluding(s => s.PropertyInfo.Name = "Id"));
Solution 4 - C#
There are a few valid answers here, but I am adding another one that does not involve stringly-typed expressions.
expectedA.ShouldBeEquivalentTo(expectedB, o => o.Excluding(s => s.Children));
expectedA.Children.ShouldBeEquivalentTo(expectedB.Children, o => o.Excluding(s => s.Id));
Solution 5 - C#
The ShouldBeEquivalentTo
method seems to be obsolete now, in order to get path for the accepted answer you can use the Excluding
overload with IMemberInfo.SelectedMemberPath
instead:
expected.Should().BeEquivalentTo(actualA, options =>
options.Excluding((IMemberInfo mi) => mi.SelectedMemberPath.EndsWith("ID")));
Solution 6 - C#
Based on RegEx match idea from Dennis Doomen‘s answer I was able to make it working
expected.ShouldBeEquivalentTo(actualA, options =>
options.Excluding(su =>
(Regex.IsMatch(su.SelectedMemberPath, "Children\\[.+\\].ID"));
Difference with Dennis answer: passing su.SelectedMemberPath, double back slashes to escape square brackets.
Solution 7 - C#
The easiest way is:
expected.ShouldBeEquivalentTo(actual, config => config.ExcludingMissingMembers());
Solution 8 - C#
I thinks the syntax is something like
actual.Should().BeEquivalentTo(
expected,
config => config.Excluding(o => o.Id).Excluding(o => o.CreateDateUtc) });
Solution 9 - C#
actual.Should().BeEquivalentTo(expected,
assertionOptions => assertionOptions
.Excluding(x => x.CreationTimestamp))
BUT if you work with structs and class overriding equals, then you should change the default comparing with ComparingByMembers https://fluentassertions.com/objectgraphs/#value-types
actual.Should().BeEquivalentTo(expected,
assertionOptions => assertionOptions
.Excluding(x => x.CreationTimestamp)
.ComparingByMembers<T>())
Solution 10 - C#
An extension class where you can pass a list of expressions
public static class FluentAssertionsExtensions {
public static EquivalencyAssertionOptions<T> ExcludingNextProperties<T>(
this EquivalencyAssertionOptions<T> options,
params Expression<Func<T, object>>[] expressions) {
foreach (var expression in expressions) {
options.Excluding(expression);
}
return options;
}
}
Usage
actual.Should().BeEquivalentTo(expected,
config => config.ExcludingNextProperties(
o => o.Id,
o => o.CreateDateUtc))