Search code examples
c#unit-testingvalidationfluent-assertions

Evaluate all properties in one assertion with the FluentAssertions framework


Context:

We need to assert object responses with many properties which have many permutations and quite a few of those properties are dynamic (generated GUIDs etc).

Example Scenario

When using FluentAssertions ...Should().BeEquivalentTo(...) one is able to get a list of all non matching fields in one evaluation.

So given the (C#) code:

using System;
using FluentAssertions;
                    
public class Program
{
    public class HouseResponse 
    {
        public int Windows { get; set; }
        public int Bedrooms { get; set; }       
        public int Doors { get; set; }              
        public int Bathrooms { get; set; } 
    }
    
    public static readonly HouseResponse ExpectedHouseResponse = new HouseResponse
    {
        Windows = 10,
        Bedrooms = 5,
        Doors = 2,      
        Bathrooms = 2
    };
    
    public static readonly HouseResponse ActualHouseResponse = new HouseResponse
    {
        Windows = 10,       
        Bedrooms = 5,
        Doors = 3,
        Bathrooms = 3
    };
    
    public static void Main()
    {
        ActualHouseResponse
            .Should()
            .BeEquivalentTo(ExpectedHouseResponse);
    }
}

where there are 2 properties that do not match, the output for the single assertion is:

Unhandled exception. FluentAssertions.Execution.AssertionFailedException: Expected property root.Doors to be 2, but found 3.
Expected property root.Bathrooms to be 2, but found 3.

which is very handy as you get all failures in one error message.

But for partial matches, say where we expect the number of doors to differ but always be a valid number > 0, we would have to do this instead:

    public static void Main()
    {
        ActualHouseResponse
            .Should()
            .BeEquivalentTo(ExpectedHouseResponse, config => 
                config.Excluding(x => x.Doors));
        
        ActualHouseResponse.Doors.Should().BeGreaterThan(0);
    }

which wouldn't actually hit the ActualHouseResponse.Doors.Should().BeGreaterThan(0); assertion as we are already failing on .Should().BeEquivalentTo because .Bathrooms isn't a match.

So the goal is to be able to evaluate all properties in one go. Which will:

  • Enforce that all properties are evaluated.
  • Allow us to get a summary of all failing properties in one test run (rather than having to fix one property, run the test and then see where the next one fails etc)

Something along the lines of:

    public static void Main()
    {
        ActualHouseResponse
            .Should()
            .BeEquivalentTo(ExpectedHouseResponse, config => 
                config.OverideEvaluation(x => x.Doors, doors => doors > 0));
    }

Does anyone have any ideas or has perhaps stumbled upon a bit of the FluentAssertions documentation I may have missed?

P.S I know this can be accomplished with a custom RuleBuilder and am familiar with FluentValidation but would like to keep that as a last resort.


Solution

  • You can use the Using/When combination to instruct the equivalency engine how to compare certain properties.

    ActualHouseResponse.Should().BeEquivalentTo(ExpectedHouseResponse, opt => opt
        .Using<int>(ctx => ctx.Subject.Should().BeGreaterThan(0))
        .When(e => e.Path.EndsWith(nameof(HouseResponse.Doors)))
    );
    

    https://fluentassertions.com/objectgraphs/#equivalency-comparison-behavior