SuperAPI AI Directory

dotnet-testing-complex-object-comparison

Testing & quality

View repository

Updated 10-15 23:45

About this skill

Complex Object Comparison Guide (Complex Object Comparison)

Core Use Cases

1. Deep Object Graph Comparison (Object Graph Comparison)

When you need to compare complex objects containing multiple levels of nested properties:

[Fact]
public void ComplexObject_深層結構比對_應完全相符()
{
    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);

    // 深層物件比對
    actual.Should().BeEquivalentTo(expected);
}

2. Circular Reference Handling (Circular Reference Handling)

Handling cases where circular references exist between objects:

[Fact]
public void TreeStructure_循環參照_應正確處理()
{
    // 建立具有父子雙向參照的樹狀結構
    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");

    // 處理循環參照
    actualTree.Should().BeEquivalentTo(parent, options =>
        options.IgnoringCyclicReferences()
               .WithMaxRecursionDepth(10)
    );
}

3-6. Advanced Comparison Modes

AwesomeAssertions also provides a variety of advanced comparison modes: dynamic field exclusion (excluding timestamps and auto-generated fields), nested object field exclusion, performance-optimized comparison for large data sets (selective property comparison, sampling verification strategies), and strict/loose ordering control.

For complete code examples, see references/detailed-comparison-patterns.md

Comparison Options Quick Reference

Option Method Purpose Applicable Scenario
Excluding(x => x.Property) Exclude specific properties Exclude timestamps and auto-generated fields
Including(x => x.Property) Include only specific properties Key property verification
IgnoringCyclicReferences() Ignore circular references Tree structures, bidirectional associations
WithMaxRecursionDepth(n) Limit recursion depth Deeply nested structures
WithStrictOrdering() Strict order comparison When array/collection order matters
WithoutStrictOrdering() Loose order comparison When array/collection order does not matter
WithTracing() Enable tracing Debugging complex comparison failures

Common Comparison Patterns and Solutions

Pattern 1: Entity Framework Entity Comparison

[Fact]
public void EFEntity_資料庫實體_應排除導航屬性()
{
    var expected = new Product { Id = 1, Name = "Laptop", Price = 999 };
    var actual = dbContext.Products.Find(1);

    actual.Should().BeEquivalentTo(expected, options =>
        options.ExcludingMissingMembers()  // 排除 EF 追蹤屬性
               .Excluding(p => p.CreatedAt)
               .Excluding(p => p.UpdatedAt)
    );
}

Pattern 2: API Response Comparison

[Fact]
public void ApiResponse_JSON反序列化_應忽略額外欄位()
{
    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()  // 忽略 API 額外欄位
    );
}

Pattern 3: Test Data Builder Comparison

[Fact]
public void Builder_測試資料_應匹配預期結構()
{
    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)  // 系統生成
               .Excluding(o => o.CreatedAt)
    );
}

Error Message Optimization

Provide meaningful error messages

[Fact]
public void Comparison_錯誤訊息_應清楚說明差異()
{
    var expected = new User { Name = "John", Age = 30 };
    var actual = userService.GetUser(1);

    // 使用 because 參數提供上下文
    actual.Should().BeEquivalentTo(expected, options =>
        options.Excluding(u => u.Id)
               .Because("ID 是系統自動生成的,不應納入比對")
    );
}

Use AssertionScope for batch verification

[Fact]
public void MultipleComparisons_批次驗證_應一次顯示所有失敗()
{
    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}$");
        }
    }
    // 所有失敗會一起報告,而非遇到第一個失敗就停止
}

Integration with Other Skills

This skill can be used in combination with the following skills:

  • awesome-assertions-guide: Basic assertion syntax and common APIs
  • autofixture-data-generation: Auto-generate test data
  • test-data-builder-pattern: Build complex test objects
  • unit-test-fundamentals: Unit testing fundamentals and the 3A pattern

Best Practice Recommendations

Recommended Practices

  1. Prefer property exclusion over inclusion: Unless you only need to verify a few properties, using Excluding is clearer
  2. Create reusable exclusion extension methods: Avoid repeating exclusion logic in every test
  3. Set a reasonable strategy for large data comparisons: Balance performance and verification completeness
  4. Use AssertionScope for batch verification: See all failure reasons at once
  5. Provide meaningful because explanations: Help future maintainers understand the test intent

Practices to Avoid

  1. Avoid over-relying on full object comparison: Consider verifying only key properties
  2. Avoid ignoring circular reference issues: Use IgnoringCyclicReferences() to handle them explicitly
  3. Avoid repeating exclusion logic in every test: Extract as an extension method
  4. Avoid performing full deep comparisons on large amounts of data: Use sampling or key property verification

Troubleshooting

Q1: What should I do if BeEquivalentTo performs very slowly?

A: Use the following strategies to optimize:

  • Use Including to compare only key properties
  • Use sampling verification for large amounts of data
  • Use WithMaxRecursionDepth to limit recursion depth
  • Consider using AssertKeyPropertiesOnly to quickly compare key fields

Q2: How do I handle StackOverflowException?

A: It is usually caused by circular references:

options.IgnoringCyclicReferences()
       .WithMaxRecursionDepth(10)

Q3: How do I exclude all time-related fields?

A: Use path pattern matching:

options.Excluding(ctx => ctx.Path.EndsWith("At"))
       .Excluding(ctx => ctx.Path.EndsWith("Time"))
       .Excluding(ctx => ctx.Path.Contains("Timestamp"))

Q4: The comparison fails but I cannot see the difference?

A: Enable detailed tracing:

options.WithTracing()  // 產生詳細的比對追蹤資訊

Template File Reference

This skill provides the following template files:

  • templates/comparison-patterns.cs: Examples of common comparison patterns
  • templates/exclusion-strategies.cs: Field exclusion strategies and extension methods

Output Format

  • Generate deep object comparison assertions using BeEquivalentTo
  • Include Excluding/Including property filtering settings
  • Be sure to mention circular reference handling: Even if the user does not explicitly ask, you should explain the usage of IgnoringCyclicReferences() and WithMaxRecursionDepth(n) , because deeply nested objects often encounter circular reference issues
  • Include complete test code for DTO/Entity comparison

Reference Resources

Original Article

The content of this skill is distilled from the "Old-School Software Engineer's Testing Practice - 30-Day Challenge" series of articles:

Official Documentation

Related Skills

  • awesome-assertions-guide - AwesomeAssertions basics and advanced usage
  • unit-test-fundamentals - Unit testing basics