Search code examples
c#testingnunitfluent-assertions

How to compare nested lists in object graphs using fluent assertions


If I have an expected object graph that includes a list, like so:

        var expectedExperiment = new Experiment
        {
            Number= "12345",
            AllocatedInstrument = "Instrument 1",
            Experimenters = new List<Experimenter>
            {
                new Experimenter
                {
                    Name = "Sue"
                    Role = "Scientist",
                    Id = 1,
                    Initials = "S"
                },
                new Experimenter()
                {
                    Name = "Mark",
                    Role = "Technician",
                    Id = 2,
                    Initials = "M"
                },
            }
        };

How can I compare it to my actual object when I only want to include certain properties on the list of child objects.

For example, I'd like to write something like this to compare all the parent object properties and some of the child object properties:

        actualExperiment.ShouldBeEquivalentTo(expectedExperiment, options => options
            .Including(o => o.Number)
            .Including(o => o.AllocatedInstrument)
            .Including(o => o.Experimenters.Select(e => e.Role))
            .Including(o => o.Experimenters.Select(e => e.Name)));

But I get an exception:

System.ArgumentException : Expression <o.Experimenters.Select(e => e.Role)> cannot be used to select a member.

I don't mind in this case what order the child items are in but I want to assert that properties I care about match expectation.


Solution

  • You can do this by checking run-time type and selected member path of current object:

    .Including(subjectInfo => subjectInfo.RuntimeType == typeof(Experimenter) &&
                              subjectInfo.SelectedMemberPath.EndsWith("Role"))
    

    This can be extracted to a method for further reuse:

    private Expression<Func<ISubjectInfo, bool>> BuildMemberExpression<TSource, TProperty>(Expression<Func<TSource,TProperty>> propertySelector)
        {
            var memberExpression = propertySelector.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new NotSupportedException();
            }
    
            return subjectInfo => subjectInfo.RuntimeType == typeof(TSource) && 
                                  subjectInfo.SelectedMemberPath.EndsWith(memberExpression.Member.Name);
        }
    

    And now use it this way:

    .Including(BuildMemberExpression((Experimenter e) => e.Name))
    .Including(BuildMemberExpression((Experimenter e) => e.Role))