FluentAssertions:比较含同名不同类型属性的对象
I’ve run into exactly this same issue before, and the root cause boils down to how you’re implementing your interface in the partial classes. Let’s break down the solutions, starting with the cleanest fix:
1. Fix the Interface Implementation (Recommended)
Your current partial class code is accidentally adding duplicate properties (one from the XSD generator, one from your interface implementation) instead of mapping the interface to the existing generated properties. This creates two separate header/body properties on the class—one concrete, one interface-type—with data only stored in the latter.
Instead, use explicit interface implementation to map the interface properties directly to the generated ones. This way, there’s only one set of properties, and the interface just provides a unified way to access them:
// Keep your interface as-is public interface IXsdGeneratedClass { IXsdGeneratedClassHeader header { get; set; } IXsdGeneratedClassBody body { get; set; } } // Corrected partial class implementation public partial class xsdGeneratedClass1 : IXsdGeneratedClass { // Explicitly implement the interface's header property, pointing to the generated concrete property IXsdGeneratedClassHeader IXsdGeneratedClass.header { get => this.header; set => this.header = (xsdGeneratedClass1Header)value; } IXsdGeneratedClassBody IXsdGeneratedClass.body { get => this.body; set => this.body = (xsdGeneratedClass1Body)value; } } public partial class xsdGeneratedClass2 : IXsdGeneratedClass { IXsdGeneratedClassHeader IXsdGeneratedClass.header { get => this.header; set => this.header = (xsdGeneratedClass2Header)value; } IXsdGeneratedClassBody IXsdGeneratedClass.body { get => this.body; set => this.body = (xsdGeneratedClass2Body)value; } }
With this change, all data lives in the auto-generated properties, and the interface just wraps them. FluentAssertions will now compare the actual data correctly using its default configuration—no extra setup needed.
2. Workaround If You Can’t Modify the Partial Class
If you’re stuck with the duplicate properties for some reason, you can configure FluentAssertions to ignore the concrete generated properties and only compare the interface-type ones:
Option A: Compare Only Interface Members
Use IncludingMembersImplementedIn to tell FluentAssertions to only consider properties defined by your interface:
// Example test code var actual = new xsdGeneratedClass1 { header = new XsdGeneratedClass1HeaderImpl(), // Your interface implementation body = new XsdGeneratedClass1BodyImpl() }; var expected = new xsdGeneratedClass1 { header = new XsdGeneratedClass1HeaderImpl { Id = "123" }, body = new XsdGeneratedClass1BodyImpl { Data = "test" } }; actual.Should().BeEquivalentTo(expected, options => options.IncludingMembersImplementedIn<IXsdGeneratedClass>());
Option B: Exclude Concrete Generated Properties
If you need more control, explicitly exclude the auto-generated concrete properties:
actual.Should().BeEquivalentTo(expected, options => options.Excluding(member => member.Name == nameof(xsdGeneratedClass1.header) && member.Type == typeof(xsdGeneratedClass1Header)) .Excluding(member => member.Name == nameof(xsdGeneratedClass1.body) && member.Type == typeof(xsdGeneratedClass1Body)));
For reuse across multiple classes, wrap this in an extension method:
public static class FluentAssertionsExtensions { public static EquivalencyAssertionOptions<T> ExcludeXsdConcreteProperties<T>(this EquivalencyAssertionOptions<T> options) { return options.Excluding(member => (member.Name is "header" or "body") && !member.Type.IsInterface); } } // Usage in tests actual.Should().BeEquivalentTo(expected, options => options.ExcludeXsdConcreteProperties());
Why RespectingRuntimeTypes Didn’t Work
That option is designed for polymorphic type comparisons (e.g., when your expected type is a base class but the actual object is a derived class). It doesn’t solve the problem of duplicate properties with the same name but different types—hence why it didn’t fix your issue.
内容的提问来源于stack exchange,提问作者Dennis Stuhr

