Search code examples
c#unit-testingfluent-assertions

FluentAssertions - Should().BeEquivalentTo() when properties are of different type


How to compare objects that have a property with the same name but different type?

public class A
{
    public Guid Id { get; set; }
}

public class B
{
    public string Id { get; set; }
}

public static B Map(A a){
    return new B { Id = a.Id.ToString() };
}

Version 1:

void Main()
{
    A a = new A { Id = Guid.NewGuid() };
    B b = Map(a);

    b.Should().BeEquivalentTo(a);
}

This produces the following error:

AssertionFailedException: Expected member Id to be {ff73e7c7-21f0-4f45-85fa-f26cd1ecafd0}, but found "{ff73e7c7-21f0-4f45-85fa-f26cd1ecafd0}".

The documentation suggests that custom property assertion rules could be possible using the Equivalence Comparison Behavior

Version 2:

void Main()
{
    A a = new A { Id = Guid.NewGuid() };
    B b = Map(a);

    b.Should().BeEquivalentTo(a, 
        options => options
            .Using<Guid>(ctx => ctx.Subject.Should().Be(ctx.Expectation))
            .WhenTypeIs<Guid>());
}

but it produces run-time exception if the properties are not of the same type.

AssertionFailedException: Expected member Id from subject to be a System.Guid, but found a System.String.

Any ideas?


Solution

  • Here are two approaches to compare objects with identically named members of different type.

    The first way is to specify equivalency between members named Id

    A expected = new A { Id = Guid.NewGuid() };
    B actual = Map(expected);
    
    actual.Should().BeEquivalentTo(expected,
        options => options
        .Using<object>(ctx => ctx.Subject.Should().Be(ctx.Expectation.ToString()))
        .When(info => info.SelectedMemberPath.EndsWith("Id")));
    

    The second approach uses the DifferentObjectsEquivalencyStep from https://stackoverflow.com/a/47947052/1087627 and your own Map function. It then converts instances of A into B, which are now easy to compare.

    AssertionOptions.AssertEquivalencyUsing(c => 
        c.Using(new DifferentObjectsEquivalencyStep<A, B>(Map)));
    
    A expected = new A { Id = Guid.NewGuid() };
    B actual = Map(expected);
    
    actual.Should().BeEquivalentTo(expected);
    

    There is an open issue about it.