1

I have several classes in our code that are automatically generated by the XSD generator tool from XSD file definitions. The classes look very similar with similar names, but based on the XSD schema (which we've received from an external vendor), the classes that are generated are all of different types. And these are pretty complex classes, with lots of deep nested properties and enum values. Hence, we used to work with the classes directly, because it was hard to do a generalized approach to work with the classes.

But I undertook the challenge, and I've (kind of) succeeded. To avoid code duplication when working with these classes, I have added properties to the classes using interface definitions outside the XSD generated file, so as to prevent them from being overwritten when generating the classes again, taking advantage of the partial class declaration like so:

Simplified example of XSD generated classes

public partial class xsdGeneratedClass1
{
    public xsdGeneratedClass1Header header { get; set }
    public xsdGeneratedClass1Body body { get; set; }
}

public partial class xsdGeneratedClass2
{
    public xsdGeneratedClass2Header header { get; set }
    public xsdGeneratedClass2Body body { get; set; }
}

Simplified example of an interface, where the properties also consists of type interfaces, which we've written to match the properties in the XSD generated classes

public interface IXsdGeneratedClass
{
    IXsdGeneratedClassHeader header { get; set; }
    IXsdGeneratedClassBody body { get; set; }
}

Simplified example of us implementing the interface, outside of the XSD generated file

public partial class xsdGeneratedClass1 : IXsdGeneratedClass
{
    public IXsdGeneratedClassHeader header { get; set; }
    public IXsdGeneratedClassBody body { get; set; }
}

public partial class xsdGeneratedClass2 : IXsdGeneratedClass
{
    public IXsdGeneratedClassHeader header { get; set; }
    public IXsdGeneratedClassBody body { get; set; }
}

In this simplified example, this construct allows me to work with the header and body properties using interfaces instead of the concrete implementations, for the dozens of classes that we have with the same structure, but with different class types, without editing in the autogenerated code of the XSD tool. This all works fine and dandy.

The problem comes when trying to compare the objects in our unit tests, using Fluent Assertions. It seems that Fluent Assertions has trouble knowing which properties of the instantiated objects to compare. In this simple example, an instantiated object of xsdGeneratedClass1 will have four properties:

  1. public xsdGeneratedClass1Header header { get; set }
  2. public xsdGeneratedClass1Body body { get; set; }
  3. public IXsdGeneratedClassHeader header { get; set; }
  4. public IXsdGeneratedClassBody body { get; set; }

The objects that I want to compare are the header and body properties with the interface types, since these will be the only ones that have actual data in them. The concrete class properties are always all null. So I've made tests like these:

class1.Should().BeEquivalentTo(expectedClass);

But it seems like Fluent Assertions constantly compares class1's IXsdGeneratedClassHeader header with the expectedClass' xsdGeneratedClass1Header header, which is null.

I have tried using the RespectingRuntimeTypes option, which makes the test pass, but then it seems like it doesn't compare the objects as it should. If I change a property value in the expectedClass' header property for example that I know won't match the one in class1, the test still passes.

I've tried scouring the interwebs for answers, and I've come to an end in my search, and basically am pondering on whether I should just write my own tool or make a gazillion manual assertions. Obi-Wan Assertions, please help!

  • This could the issue described in https://github.com/fluentassertions/fluentassertions/issues/1130. Also make sure that the generic type `T` of `BeEquivalentTo` is `IXsdGeneratedClass`. – Jonas Nyrup Sep 21 '19 at 19:31
  • Thank you. That solved my problem! See my post below :-) – Dennis Stuhr Sep 27 '19 at 09:09

1 Answers1

0

Thank you very much Jonas Nyrup! Seems to be a defect that is now amended in the master branch of Fluent Assertions. In the meantime, someone posted an answer in the github thread for a work-around, that also worked for me. Very happy. Thanks!

Declare this class somewhere, available for use in your unit tests

public class ReflectionMemberMatchingRule : IMemberMatchingRule
{
    public SelectedMemberInfo Match(SelectedMemberInfo expectedMember, object subject, string memberPath, IEquivalencyAssertionOptions config) => expectedMember;
}

Use the extension class in your unit tests, by adding the configuration to Fluent Assertions.

AssertionOptions.AssertEquivalencyUsing(x => x.Using(new ReflectionMemberMatchingRule()));

It now works as expected!