Search code examples
c#genericslinq-expressionsfluent-assertions

How can I dynamically select properties for equivalency test - FluentAssertions


I'm creating unit tests in which I will be comparing lists of objects with one another.

Currently I am using Fluent assertions in combination with specflow and nunit. I already use the Fluent Assertions to make a comparison as following:

public void TestShizzle()
{
    // I normally retrieve these lists from a moq database or a specflow table
    var expected = list<myObject> 
    {
        new myObject 
        {
            A = 1,
            B = "abc"
        }
    }

    var found = list<myObject> 
    {
        new myObject 
        {
            A = 1,
            B = "def"
        }
    }

    // this comparison only compares a few columns. The comparison is also object dependent. I would like to make this dynamic
    found.Should().BeEquivalentTo(
        expected,
        options =>
        options.Including(x => x.A));
}

What I really want is to be able to use generics instead of a specified type. I also want to decide which properties to compare at compile time. This is because of the large number of tables in the database. I think i need to use Linq Expressions for this, but I don't know how to go about this. The function should look something like this:

public void GenericShizzle<T>(List<T> expected, List<T> found, IEnumerable<PropertyInfo> properties)
{
    Expression<Func<T, object>> principal;
    foreach(var property in properties)
    {
        // create the expression for including fields
    }

    found.Should().BeEquivalentTo(
        expected,
        options =>
        // here is need to apply the expression.
}

I have no real idea how to get the correct expression for the job, or if this even the best method. I think I need to create an property expression that is understood by the include function, but maybe a different method can be used?


Solution

  • There is Including method overload accepting Expression<Func<IMemberInfo, bool>> which can be used to dynamically filter members based on information about them:

    IEnumerable<PropertyInfo> properties = ...;
    var names = properties
        .Select(info => info.Name)
        .ToHashSet();
    found.Should()
        .BeEquivalentTo(expected,
             options => options.Including((IMemberInfo mi) => names.Contains(mi.Name))); // or just .Including(mi => names.Contains(mi.Name))