Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.
Complex Object Comparison Guide
Applicable Scenarios
This skill focuses on complex object comparison scenarios in .NET testing, using AwesomeAssertions' BeEquivalentTo API
to handle various advanced comparison needs.
Core Usage Scenarios
1. Deep Object Structure Comparison (Object Graph Comparison)
When comparing complex objects containing multi-layer nested properties:
[Fact]
public void ComplexObject_Deep_Structure_Comparison_Should_Match()
{
var expected = new Order
{
Id = 1,
Customer = new Customer
{
Name = "John Doe",
Address = new Address
{
Street = "123 Main St",
City = "Seattle",
ZipCode = "98101"
}
},
Items = new[]
{
new OrderItem { ProductName = "Laptop", Quantity = 1, Price = 999.99m },
new OrderItem { ProductName = "Mouse", Quantity = 2, Price = 29.99m }
}
};
var actual = orderService.GetOrder(1);
// Deep object comparison
actual.Should().BeEquivalentTo(expected);
}
```text
### 2. Circular Reference Handling (Circular Reference Handling)
Handling cases where objects have circular references:
```csharp
[Fact]
public void TreeStructure_Circular_Reference_Should_Handle_Correctly()
{
// Create tree structure with parent-child bidirectional references
var parent = new TreeNode { Value = "Root" };
var child1 = new TreeNode { Value = "Child1", Parent = parent };
var child2 = new TreeNode { Value = "Child2", Parent = parent };
parent.Children = new[] { child1, child2 };
var actualTree = treeService.GetTree("Root");
// Handle circular references
actualTree.Should().BeEquivalentTo(parent, options =>
options.IgnoringCyclicReferences()
.WithMaxRecursionDepth(10)
);
}
```text
### 3-6. Advanced Comparison Patterns
FluentAssertions provides various advanced comparison patterns: dynamic field exclusion (excluding timestamps, auto-generated fields), nested object field exclusion, performance-optimized comparison for large data (selective property comparison, sampling validation strategies), and strict/loose ordering control.
> For complete code examples, see [references/detailed-comparison-patterns.md](references/detailed-comparison-patterns.md)
## Comparison Options Quick Reference
| Option Method | Purpose | Applicable Scenario |
| ---------------------------- | -------------------- | ------------------------------------ |
| `Excluding(x => x.Property)` | Exclude specific property | Exclude timestamps, auto-generated fields |
| `Including(x => x.Property)` | Include only specific property | Key property validation |
| `IgnoringCyclicReferences()` | Ignore circular references | Tree structures, bidirectional associations |
| `WithMaxRecursionDepth(n)` | Limit recursion depth | Deep nested structures |
| `WithStrictOrdering()` | Strict ordering comparison | When array/collection order matters |
| `WithoutStrictOrdering()` | Loose ordering comparison | When array/collection order doesn't matter |
| `WithTracing()` | Enable tracing | Debugging complex comparison failures |
## Common Comparison Patterns and Solutions
### Pattern 1: Entity Framework Entity Comparison
```csharp
[Fact]
public void EFEntity_Database_Entity_Should_Exclude_Navigation_Properties()
{
var expected = new Product { Id = 1, Name = "Laptop", Price = 999 };
var actual = dbContext.Products.Find(1);
actual.Should().BeEquivalentTo(expected, options =>
options.ExcludingMissingMembers() // Exclude EF tracking properties
.Excluding(p => p.CreatedAt)
.Excluding(p => p.UpdatedAt)
);
}
```text
### Pattern 2: API Response Comparison
```csharp
[Fact]
public void ApiResponse_JSON_Deserialization_Should_Ignore_Extra_Fields()
{
var expected = new UserDto
{
Id = 1,
Username = "john_doe"
};
var response = await httpClient.GetAsync("/api/users/1");
var actual = await response.Content.ReadFromJsonAsync<UserDto>();
actual.Should().BeEquivalentTo(expected, options =>
options.ExcludingMissingMembers() // Ignore API extra fields
);
}
```text
### Pattern 3: Test Data Builder Comparison
```csharp
[Fact]
public void Builder_Test_Data_Should_Match_Expected_Structure()
{
var expected = new OrderBuilder()
.WithId(1)
.WithCustomer("John Doe")
.WithItems(3)
.Build();
var actual = orderService.CreateOrder(orderRequest);
actual.Should().BeEquivalentTo(expected, options =>
options.Excluding(o => o.OrderNumber) // System generated
.Excluding(o => o.CreatedAt)
);
}
```text
## Error Message Optimization
### Providing Meaningful Error Messages
```csharp
[Fact]
public void Comparison_Error_Message_Should_Clearly_Explain_Differences()
{
var expected = new User { Name = "John", Age = 30 };
var actual = userService.GetUser(1);
// Use because parameter to provide context
actual.Should().BeEquivalentTo(expected, options =>
options.Excluding(u => u.Id)
.Because("ID is system-generated and should not be included in comparison")
);
}
```text
### Using AssertionScope for Batch Validation
```csharp
[Fact]
public void MultipleComparisons_Batch_Validation_Should_Show_All_Failures()
{
var users = userService.GetAllUsers();
using (new AssertionScope())
{
foreach (var user in users)
{
user.Id.Should().BeGreaterThan(0);
user.Name.Should().NotBeNullOrEmpty();
user.Email.Should().MatchRegex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");
}
}
// All failures reported together, rather than stopping at first failure
}
```text
## Integration with Other Skills
This skill can be combined with the following:
- **awesome-assertions-guide**: Basic assertion syntax and common APIs
- **autofixture-data-generation**: Automatically generate test data
- **test-data-builder-pattern**: Build complex test objects
- **unit-test-fundamentals**: Unit testing basics and 3A pattern
## Best Practice Recommendations
### ✅ Recommended Practices
1. **Prefer Property Exclusion over Inclusion**: Unless only validating a few properties, using `Excluding` is clearer
2. **Create Reusable Exclusion Extension Methods**: Avoid repeating exclusion logic in each test
3. **Set Reasonable Strategies for Large Data Comparison**: Balance performance and validation completeness
4. **Use AssertionScope for Batch Validation**: See all failure reasons at once
5. **Provide Meaningful because Descriptions**: Help future maintainers understand test intent
### ❌ Practices to Avoid
1. **Avoid Over-reliance on Complete Object Comparison**: Consider only validating key properties
2. **Avoid Ignoring Circular Reference Issues**: Use `IgnoringCyclicReferences()` to explicitly handle
3. **Avoid Repeating Exclusion Logic in Each Test**: Extract as extension methods
4. **Avoid Full Deep Comparison for Large Data**: Use sampling or key property validation
## Troubleshooting
### Q1: BeEquivalentTo Performance is Slow?
**A:** Use the following strategies to optimize:
- Use `Including` to only compare key properties
- Use sampling validation for large data
- Use `WithMaxRecursionDepth` to limit recursion depth
- Consider using `AssertKeyPropertiesOnly` for quick comparison of key fields
### Q2: How to Handle StackOverflowException?
**A:** Usually caused by circular references:
```csharp
options.IgnoringCyclicReferences()
.WithMaxRecursionDepth(10)
```text
### Q3: How to Exclude All Time-Related Fields?
**A:** Use path pattern matching:
```csharp
options.Excluding(ctx => ctx.Path.EndsWith("At"))
.Excluding(ctx => ctx.Path.EndsWith("Time"))
.Excluding(ctx => ctx.Path.Contains("Timestamp"))
```text
### Q4: Comparison Fails but Can't See the Difference?
**A:** Enable detailed tracing:
```csharp
options.WithTracing() // Generate detailed comparison trace information
```text
## Template Files Reference
This skill provides the following template files:
- `templates/comparison-patterns.cs`: Common comparison pattern examples
- `templates/exclusion-strategies.cs`: Field exclusion strategies and extension methods
## Reference Resources
### Original Articles
This skill content is distilled from the "Old School Software Engineer's Testing Practice - 30 Day Challenge" article series:
- **Day 05 - AwesomeAssertions Advanced Techniques and Complex Scenario Applications**
- Article: https://ithelp.ithome.com.tw/articles/10374425
- Sample Code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day05
### Official Documentation
- [AwesomeAssertions GitHub](https://github.com/AwesomeAssertions/AwesomeAssertions)
- [AwesomeAssertions Documentation](https://awesomeassertions.org/)
### Related Skills
- `awesome-assertions-guide` - AwesomeAssertions basics and advanced usage
- `unit-test-fundamentals` - Unit testing basics
1---2name: dotnet-testing-complex-object-comparison3category: testing4subcategory: assertions5description: |6 Specialized skill for handling complex object comparison and deep validation. Use when you need to compare deep objects, exclude specific properties, handle circular references, or validate DTOs/Entities. Covers BeEquivalentTo, Excluding, Including, custom comparison rules, etc.7 Keywords: object comparison, deep comparison, BeEquivalentTo, DTO comparison, Entity validation, excluding properties, circular reference, Excluding, Including, ExcludingNestedObjects, RespectingRuntimeTypes, WithStrictOrdering, ignore timestamp, exclude timestamp8targets: ['*']9license: MIT10metadata:11 author: Kevin Tseng12 version: '1.0.0'13 tags: '.NET, testing, object comparison, BeEquivalentTo, AwesomeAssertions'14 related_skills: 'awesome-assertions-guide, autofixture-basics, test-data-builder-pattern'15claudecode: {}16opencode: {}17codexcli:18 short-description: '.NET skill guidance for dotnet-testing-complex-object-comparison'19copilot: {}20geminicli: {}21antigravity: {}22---2324Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.2526# Complex Object Comparison Guide2728## Applicable Scenarios2930This skill focuses on complex object comparison scenarios in .NET testing, using AwesomeAssertions' `BeEquivalentTo` API31to handle various advanced comparison needs.3233## Core Usage Scenarios3435### 1. Deep Object Structure Comparison (Object Graph Comparison)3637When comparing complex objects containing multi-layer nested properties:3839````csharp40[Fact]41public void ComplexObject_Deep_Structure_Comparison_Should_Match()42{43 var expected = new Order44 {45 Id = 1,46 Customer = new Customer47 {48 Name = "John Doe",49 Address = new Address50 {51 Street = "123 Main St",52 City = "Seattle",53 ZipCode = "98101"54 }55 },56 Items = new[]57 {58 new OrderItem { ProductName = "Laptop", Quantity = 1, Price = 999.99m },59 new OrderItem { ProductName = "Mouse", Quantity = 2, Price = 29.99m }60 }61 };6263 var actual = orderService.GetOrder(1);6465 // Deep object comparison66 actual.Should().BeEquivalentTo(expected);67}68```text6970### 2. Circular Reference Handling (Circular Reference Handling)7172Handling cases where objects have circular references:7374```csharp75[Fact]76public void TreeStructure_Circular_Reference_Should_Handle_Correctly()77{78 // Create tree structure with parent-child bidirectional references79 var parent = new TreeNode { Value = "Root" };80 var child1 = new TreeNode { Value = "Child1", Parent = parent };81 var child2 = new TreeNode { Value = "Child2", Parent = parent };82 parent.Children = new[] { child1, child2 };8384 var actualTree = treeService.GetTree("Root");8586 // Handle circular references87 actualTree.Should().BeEquivalentTo(parent, options =>88 options.IgnoringCyclicReferences()89 .WithMaxRecursionDepth(10)90 );91}92```text9394### 3-6. Advanced Comparison Patterns9596FluentAssertions provides various advanced comparison patterns: dynamic field exclusion (excluding timestamps, auto-generated fields), nested object field exclusion, performance-optimized comparison for large data (selective property comparison, sampling validation strategies), and strict/loose ordering control.9798> For complete code examples, see [references/detailed-comparison-patterns.md](references/detailed-comparison-patterns.md)99100## Comparison Options Quick Reference101102| Option Method | Purpose | Applicable Scenario |103| ---------------------------- | -------------------- | ------------------------------------ |104| `Excluding(x => x.Property)` | Exclude specific property | Exclude timestamps, auto-generated fields |105| `Including(x => x.Property)` | Include only specific property | Key property validation |106| `IgnoringCyclicReferences()` | Ignore circular references | Tree structures, bidirectional associations |107| `WithMaxRecursionDepth(n)` | Limit recursion depth | Deep nested structures |108| `WithStrictOrdering()` | Strict ordering comparison | When array/collection order matters |109| `WithoutStrictOrdering()` | Loose ordering comparison | When array/collection order doesn't matter |110| `WithTracing()` | Enable tracing | Debugging complex comparison failures |111112## Common Comparison Patterns and Solutions113114### Pattern 1: Entity Framework Entity Comparison115116```csharp117[Fact]118public void EFEntity_Database_Entity_Should_Exclude_Navigation_Properties()119{120 var expected = new Product { Id = 1, Name = "Laptop", Price = 999 };121 var actual = dbContext.Products.Find(1);122123 actual.Should().BeEquivalentTo(expected, options =>124 options.ExcludingMissingMembers() // Exclude EF tracking properties125 .Excluding(p => p.CreatedAt)126 .Excluding(p => p.UpdatedAt)127 );128}129```text130131### Pattern 2: API Response Comparison132133```csharp134[Fact]135public void ApiResponse_JSON_Deserialization_Should_Ignore_Extra_Fields()136{137 var expected = new UserDto138 {139 Id = 1,140 Username = "john_doe"141 };142143 var response = await httpClient.GetAsync("/api/users/1");144 var actual = await response.Content.ReadFromJsonAsync<UserDto>();145146 actual.Should().BeEquivalentTo(expected, options =>147 options.ExcludingMissingMembers() // Ignore API extra fields148 );149}150```text151152### Pattern 3: Test Data Builder Comparison153154```csharp155[Fact]156public void Builder_Test_Data_Should_Match_Expected_Structure()157{158 var expected = new OrderBuilder()159 .WithId(1)160 .WithCustomer("John Doe")161 .WithItems(3)162 .Build();163164 var actual = orderService.CreateOrder(orderRequest);165166 actual.Should().BeEquivalentTo(expected, options =>167 options.Excluding(o => o.OrderNumber) // System generated168 .Excluding(o => o.CreatedAt)169 );170}171```text172173## Error Message Optimization174175### Providing Meaningful Error Messages176177```csharp178[Fact]179public void Comparison_Error_Message_Should_Clearly_Explain_Differences()180{181 var expected = new User { Name = "John", Age = 30 };182 var actual = userService.GetUser(1);183184 // Use because parameter to provide context185 actual.Should().BeEquivalentTo(expected, options =>186 options.Excluding(u => u.Id)187 .Because("ID is system-generated and should not be included in comparison")188 );189}190```text191192### Using AssertionScope for Batch Validation193194```csharp195[Fact]196public void MultipleComparisons_Batch_Validation_Should_Show_All_Failures()197{198 var users = userService.GetAllUsers();199200 using (new AssertionScope())201 {202 foreach (var user in users)203 {204 user.Id.Should().BeGreaterThan(0);205 user.Name.Should().NotBeNullOrEmpty();206 user.Email.Should().MatchRegex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");207 }208 }209 // All failures reported together, rather than stopping at first failure210}211```text212213## Integration with Other Skills214215This skill can be combined with the following:216217- **awesome-assertions-guide**: Basic assertion syntax and common APIs218- **autofixture-data-generation**: Automatically generate test data219- **test-data-builder-pattern**: Build complex test objects220- **unit-test-fundamentals**: Unit testing basics and 3A pattern221222## Best Practice Recommendations223224### ✅ Recommended Practices2252261. **Prefer Property Exclusion over Inclusion**: Unless only validating a few properties, using `Excluding` is clearer2272. **Create Reusable Exclusion Extension Methods**: Avoid repeating exclusion logic in each test2283. **Set Reasonable Strategies for Large Data Comparison**: Balance performance and validation completeness2294. **Use AssertionScope for Batch Validation**: See all failure reasons at once2305. **Provide Meaningful because Descriptions**: Help future maintainers understand test intent231232### ❌ Practices to Avoid2332341. **Avoid Over-reliance on Complete Object Comparison**: Consider only validating key properties2352. **Avoid Ignoring Circular Reference Issues**: Use `IgnoringCyclicReferences()` to explicitly handle2363. **Avoid Repeating Exclusion Logic in Each Test**: Extract as extension methods2374. **Avoid Full Deep Comparison for Large Data**: Use sampling or key property validation238239## Troubleshooting240241### Q1: BeEquivalentTo Performance is Slow?242243**A:** Use the following strategies to optimize:244245- Use `Including` to only compare key properties246- Use sampling validation for large data247- Use `WithMaxRecursionDepth` to limit recursion depth248- Consider using `AssertKeyPropertiesOnly` for quick comparison of key fields249250### Q2: How to Handle StackOverflowException?251252**A:** Usually caused by circular references:253254```csharp255options.IgnoringCyclicReferences()256 .WithMaxRecursionDepth(10)257```text258259### Q3: How to Exclude All Time-Related Fields?260261**A:** Use path pattern matching:262263```csharp264options.Excluding(ctx => ctx.Path.EndsWith("At"))265 .Excluding(ctx => ctx.Path.EndsWith("Time"))266 .Excluding(ctx => ctx.Path.Contains("Timestamp"))267```text268269### Q4: Comparison Fails but Can't See the Difference?270271**A:** Enable detailed tracing:272273```csharp274options.WithTracing() // Generate detailed comparison trace information275```text276277## Template Files Reference278279This skill provides the following template files:280281- `templates/comparison-patterns.cs`: Common comparison pattern examples282- `templates/exclusion-strategies.cs`: Field exclusion strategies and extension methods283284## Reference Resources285286### Original Articles287288This skill content is distilled from the "Old School Software Engineer's Testing Practice - 30 Day Challenge" article series:289290- **Day 05 - AwesomeAssertions Advanced Techniques and Complex Scenario Applications**291 - Article: https://ithelp.ithome.com.tw/articles/10374425292 - Sample Code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day05293294### Official Documentation295296- [AwesomeAssertions GitHub](https://github.com/AwesomeAssertions/AwesomeAssertions)297- [AwesomeAssertions Documentation](https://awesomeassertions.org/)298299### Related Skills300301- `awesome-assertions-guide` - AwesomeAssertions basics and advanced usage302- `unit-test-fundamentals` - Unit testing basics303````