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
- Prefer property exclusion over inclusion: Unless you only need to verify a few properties, using
Excludingis clearer - Create reusable exclusion extension methods: Avoid repeating exclusion logic in every test
- Set a reasonable strategy for large data comparisons: Balance performance and verification completeness
- Use AssertionScope for batch verification: See all failure reasons at once
- Provide meaningful because explanations: Help future maintainers understand the test intent
Practices to Avoid
- Avoid over-relying on full object comparison: Consider verifying only key properties
- Avoid ignoring circular reference issues: Use
IgnoringCyclicReferences()to handle them explicitly - Avoid repeating exclusion logic in every test: Extract as an extension method
- 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
Includingto compare only key properties - Use sampling verification for large amounts of data
- Use
WithMaxRecursionDepthto limit recursion depth - Consider using
AssertKeyPropertiesOnlyto 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 patternstemplates/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()andWithMaxRecursionDepth(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:
- Day 05 - AwesomeAssertions Advanced Techniques and Complex Scenario Applications
- Ironman series article:https://ithelp.ithome.com.tw/articles/10374425
- Sample code:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day05
Official Documentation
Related Skills
awesome-assertions-guide- AwesomeAssertions basics and advanced usageunit-test-fundamentals- Unit testing basics