Skip to content

Commit 7a4e771

Browse files
thomhurstclaude
andauthored
Documentation Updates (#2720)
* docs: Comprehensive documentation improvements - Fixed version placeholder in installation.md (changed from $(TUnitVersion) to *) - Fixed syntax error in depends-on.md array syntax - Created advanced/extension-points.md documenting ITestExecutor, IHookExecutor, and event receivers - Created advanced/exception-handling.md with complete exception hierarchy documentation - Created test-authoring/generic-attributes.md for type-safe generic attribute usage - Created troubleshooting.md with common issues and solutions - Created advanced/performance-best-practices.md for test performance optimization - Enhanced test-context.md with service provider integration documentation - Enhanced awaiting.md with extensive complex assertion examples These improvements address missing documentation for key TUnit features and provide clearer guidance for developers with practical examples and best practices. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Fix incorrect attribute names in extension-points.md - Changed UseTestExecutor to TestExecutor (the correct attribute name) - Changed RegisterEventReceiver to proper attribute-based registration - Added examples showing event receivers are implemented as attributes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Fix event receiver interface signatures in extension-points.md - Fixed ITestDiscoveryEventReceiver to have only OnTestDiscovered method - Changed all event receiver methods to return ValueTask instead of Task - Fixed parameter types (TestRegisteredContext, no TestResult in OnTestEnd) - Fixed ITestRetryEventReceiver signature (only context and retryAttempt) - Updated example to show event receivers as attributes with correct signatures 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Fix MDX compilation error by escaping angle brackets in headings - Escaped <T> to &lt;T&gt; in all generic attribute headings - Prevents MDX from interpreting <T> as an HTML tag 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Add comprehensive nested property injection documentation - Added detailed section on nested property injection with automatic initialization - Included real-world example with test containers and WebApplicationFactory - Documented how TUnit resolves dependency graphs and manages object lifetimes - Added best practices and sharing strategies - Covered advanced scenarios including circular dependency detection This powerful feature enables advanced test orchestration with simple code, letting TUnit handle initialization order and object lifetimes automatically. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4178ceb commit 7a4e771

File tree

10 files changed

+3278
-2
lines changed

10 files changed

+3278
-2
lines changed

docs/docs/advanced/exception-handling.md

Lines changed: 509 additions & 0 deletions
Large diffs are not rendered by default.

docs/docs/advanced/extension-points.md

Lines changed: 446 additions & 0 deletions
Large diffs are not rendered by default.

docs/docs/advanced/performance-best-practices.md

Lines changed: 556 additions & 0 deletions
Large diffs are not rendered by default.

docs/docs/assertions/awaiting.md

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,291 @@ This won't:
3535

3636
TUnit is able to take in asynchronous delegates. To be able to assert on these, we need to execute the code. We want to avoid sync-over-async, as this can cause problems and block the thread pool, slowing down your test suite.
3737
And with how fast .NET has become, the overhead of `Task`s and `async` methods shouldn't be noticable.
38+
39+
## Complex Assertion Examples
40+
41+
### Chaining Multiple Assertions
42+
43+
You can chain multiple assertions together for more complex validations:
44+
45+
```csharp
46+
[Test]
47+
public async Task ComplexObjectValidation()
48+
{
49+
var user = await GetUserAsync("john.doe");
50+
51+
// Chain multiple property assertions
52+
await Assert.That(user)
53+
.IsNotNull()
54+
.And.HasProperty(u => u.Email).EqualTo("john.doe@example.com")
55+
.And.HasProperty(u => u.Age).GreaterThan(18)
56+
.And.HasProperty(u => u.Roles).Contains("Admin");
57+
}
58+
```
59+
60+
### Collection Assertions with Complex Conditions
61+
62+
```csharp
63+
[Test]
64+
public async Task ComplexCollectionAssertions()
65+
{
66+
var orders = await GetOrdersAsync();
67+
68+
// Assert multiple conditions on a collection
69+
await Assert.That(orders)
70+
.HasCount().GreaterThan(0)
71+
.And.Contains(o => o.Status == OrderStatus.Completed)
72+
.And.DoesNotContain(o => o.Total < 0)
73+
.And.HasDistinctItems(new OrderIdComparer());
74+
75+
// Assert on filtered subset
76+
var completedOrders = orders.Where(o => o.Status == OrderStatus.Completed);
77+
await Assert.That(completedOrders)
78+
.All(o => o.CompletedDate != null)
79+
.And.Any(o => o.Total > 1000);
80+
}
81+
```
82+
83+
### Async Operation Assertions
84+
85+
```csharp
86+
[Test]
87+
public async Task AsyncOperationAssertions()
88+
{
89+
// Assert that async operation completes within time limit
90+
await Assert.That(async () => await LongRunningOperationAsync())
91+
.CompletesWithin(TimeSpan.FromSeconds(5));
92+
93+
// Assert that async operation throws specific exception
94+
await Assert.That(async () => await RiskyOperationAsync())
95+
.Throws<InvalidOperationException>()
96+
.WithMessage().Containing("connection failed");
97+
98+
// Assert on result of async operation
99+
await Assert.That(() => CalculateAsync(10, 20))
100+
.ResultsIn(30)
101+
.Within(TimeSpan.FromSeconds(1));
102+
}
103+
```
104+
105+
### Nested Object Assertions
106+
107+
```csharp
108+
[Test]
109+
public async Task NestedObjectAssertions()
110+
{
111+
var company = await GetCompanyAsync();
112+
113+
await Assert.That(company)
114+
.IsNotNull()
115+
.And.HasProperty(c => c.Name).EqualTo("TechCorp")
116+
.And.HasProperty(c => c.Address).Satisfies(address =>
117+
Assert.That(address)
118+
.HasProperty(a => a.City).EqualTo("Seattle")
119+
.And.HasProperty(a => a.ZipCode).Matches(@"^\d{5}$")
120+
)
121+
.And.HasProperty(c => c.Employees).Satisfies(employees =>
122+
Assert.That(employees)
123+
.HasCount().Between(100, 500)
124+
.And.All(e => e.Email.EndsWith("@techcorp.com"))
125+
);
126+
}
127+
```
128+
129+
### Exception Assertions with Details
130+
131+
```csharp
132+
[Test]
133+
public async Task DetailedExceptionAssertions()
134+
{
135+
var invalidData = new { Id = -1, Name = "" };
136+
137+
// Assert exception with specific properties
138+
await Assert.That(() => ProcessDataAsync(invalidData))
139+
.Throws<ValidationException>()
140+
.WithMessage().EqualTo("Validation failed")
141+
.And.WithInnerException<ArgumentException>()
142+
.WithParameterName("data");
143+
144+
// Assert aggregate exception
145+
await Assert.That(() => ParallelOperationAsync())
146+
.Throws<AggregateException>()
147+
.Where(ex => ex.InnerExceptions.Count == 3)
148+
.And.Where(ex => ex.InnerExceptions.All(e => e is TaskCanceledException));
149+
}
150+
```
151+
152+
### Custom Assertion Conditions
153+
154+
```csharp
155+
[Test]
156+
public async Task CustomAssertionConditions()
157+
{
158+
var measurements = await GetMeasurementsAsync();
159+
160+
// Use custom conditions for complex validations
161+
await Assert.That(measurements)
162+
.Satisfies(m => {
163+
var average = m.Average();
164+
var stdDev = CalculateStandardDeviation(m);
165+
return stdDev < average * 0.1; // Less than 10% deviation
166+
}, "Measurements should have low standard deviation");
167+
168+
// Combine built-in and custom assertions
169+
await Assert.That(measurements)
170+
.HasCount().GreaterThan(100)
171+
.And.All(m => m > 0)
172+
.And.Satisfies(IsNormallyDistributed, "Data should be normally distributed");
173+
}
174+
```
175+
176+
### DateTime and TimeSpan Assertions
177+
178+
```csharp
179+
[Test]
180+
public async Task DateTimeAssertions()
181+
{
182+
var order = await CreateOrderAsync();
183+
184+
// Complex datetime assertions
185+
await Assert.That(order.CreatedAt)
186+
.IsAfter(DateTime.UtcNow.AddMinutes(-1))
187+
.And.IsBefore(DateTime.UtcNow.AddMinutes(1))
188+
.And.HasKind(DateTimeKind.Utc);
189+
190+
// TimeSpan assertions
191+
var processingTime = order.CompletedAt - order.CreatedAt;
192+
await Assert.That(processingTime)
193+
.IsLessThan(TimeSpan.FromMinutes(5))
194+
.And.IsGreaterThan(TimeSpan.Zero);
195+
}
196+
```
197+
198+
### Floating Point Comparisons
199+
200+
```csharp
201+
[Test]
202+
public async Task FloatingPointAssertions()
203+
{
204+
var calculations = await PerformComplexCalculationsAsync();
205+
206+
// Use tolerance for floating point comparisons
207+
await Assert.That(calculations.Pi)
208+
.IsEqualTo(Math.PI).Within(0.0001);
209+
210+
// Assert on collections of floating point numbers
211+
await Assert.That(calculations.Results)
212+
.All(r => Math.Abs(r) < 1000000) // No overflow
213+
.And.Contains(42.0).Within(0.1) // Contains approximately 42
214+
.And.HasSum().EqualTo(expectedSum).Within(0.01);
215+
}
216+
```
217+
218+
### String Pattern Matching
219+
220+
```csharp
221+
[Test]
222+
public async Task StringPatternAssertions()
223+
{
224+
var logs = await GetLogEntriesAsync();
225+
226+
// Complex string assertions
227+
await Assert.That(logs)
228+
.All(log => log.Matches(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]"))
229+
.And.Any(log => log.Contains("ERROR"))
230+
.And.None(log => log.Contains("SENSITIVE_DATA"));
231+
232+
// Assert on formatted output
233+
var report = await GenerateReportAsync();
234+
await Assert.That(report)
235+
.StartsWith("Report Generated:")
236+
.And.Contains("Total Items:")
237+
.And.DoesNotContain("null")
238+
.And.HasLength().Between(1000, 5000);
239+
}
240+
```
241+
242+
### Combining Or and And Conditions
243+
244+
```csharp
245+
[Test]
246+
public async Task ComplexLogicalConditions()
247+
{
248+
var product = await GetProductAsync();
249+
250+
// Complex logical combinations
251+
await Assert.That(product)
252+
.HasProperty(p => p.Status)
253+
.EqualTo(ProductStatus.Active)
254+
.Or.EqualTo(ProductStatus.Pending)
255+
.And.HasProperty(p => p.Price)
256+
.GreaterThan(0)
257+
.And.LessThan(10000);
258+
259+
// Multiple condition paths
260+
await Assert.That(product.Category)
261+
.IsEqualTo("Electronics")
262+
.And(Assert.That(product.Warranty).IsNotNull())
263+
.Or
264+
.IsEqualTo("Books")
265+
.And(Assert.That(product.ISBN).IsNotNull());
266+
}
267+
```
268+
269+
### Performance Assertions
270+
271+
```csharp
272+
[Test]
273+
public async Task PerformanceAssertions()
274+
{
275+
var stopwatch = Stopwatch.StartNew();
276+
var results = new List<long>();
277+
278+
// Measure multiple operations
279+
for (int i = 0; i < 100; i++)
280+
{
281+
var start = stopwatch.ElapsedMilliseconds;
282+
await PerformOperationAsync();
283+
results.Add(stopwatch.ElapsedMilliseconds - start);
284+
}
285+
286+
// Assert on performance metrics
287+
await Assert.That(results.Average())
288+
.IsLessThan(100); // Average under 100ms
289+
290+
await Assert.That(results.Max())
291+
.IsLessThan(500); // No operation over 500ms
292+
293+
await Assert.That(results.Where(r => r > 200).Count())
294+
.IsLessThan(5); // Less than 5% over 200ms
295+
}
296+
```
297+
298+
### State Machine Assertions
299+
300+
```csharp
301+
[Test]
302+
public async Task StateMachineAssertions()
303+
{
304+
var workflow = new OrderWorkflow();
305+
306+
// Initial state
307+
await Assert.That(workflow.State).IsEqualTo(OrderState.New);
308+
309+
// State transition assertions
310+
await workflow.StartProcessing();
311+
await Assert.That(workflow.State)
312+
.IsEqualTo(OrderState.Processing)
313+
.And(Assert.That(workflow.CanTransitionTo(OrderState.Completed)).IsTrue())
314+
.And(Assert.That(workflow.CanTransitionTo(OrderState.New)).IsFalse());
315+
316+
// Complex workflow validation
317+
await workflow.Complete();
318+
await Assert.That(workflow)
319+
.HasProperty(w => w.State).EqualTo(OrderState.Completed)
320+
.And.HasProperty(w => w.CompletedAt).IsNotNull()
321+
.And.HasProperty(w => w.History).Contains(h => h.State == OrderState.Processing);
322+
}
323+
```
324+
325+
These examples demonstrate the power and flexibility of TUnit's assertion system, showing how you can build complex, readable assertions for various testing scenarios.

docs/docs/getting-started/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Your `.csproj` should be as simple as something like:
4242
</PropertyGroup>
4343

4444
<ItemGroup>
45-
<PackageReference Include="TUnit" Version="$(TUnitVersion)" />
45+
<PackageReference Include="TUnit" Version="*" />
4646
</ItemGroup>
4747

4848
</Project>

docs/docs/test-authoring/depends-on.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ e.g.:
3131
```csharp
3232
public void Test1(string value1, int value2) { ... }
3333

34-
[DependsOn(nameof(Test1), [typeof(string), typeof(int)])]
34+
[DependsOn(nameof(Test1), new[] { typeof(string), typeof(int) })]
3535
public void Test2() { ... }
3636
```
3737

0 commit comments

Comments
 (0)