diff --git a/.claude/docs/workflows.md b/.claude/docs/workflows.md
index 845d58a47f4..601ad030aaf 100644
--- a/.claude/docs/workflows.md
+++ b/.claude/docs/workflows.md
@@ -141,11 +141,13 @@ Use a directive immediately before a fence when its context needs to be explicit
-
-
```
-Use `` on each fence that depends on surrounding prose. Its page must declare ``, so unmarked fences still compile and cannot be silently excluded by page-level configuration. Use `` when no snippets on a page can compile independently. File directives require a reason.
+Every C# fence must compile with warnings as errors. Failure-masking ignore/contextual directives,
+warning pragmas, nullable disabling, `#if false`, and suppression attributes are rejected. The verifier
+also fails unless `NoWarn` and `WarningsNotAsErrors` are empty.
+
+Use `` once on tutorial pages whose fences intentionally share declarations. Every fence still compiles; generated snippets use one page-scoped namespace.
For a fence that mixes declarations or members with usage, split it explicitly at an exact marker:
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 46d4a9b49a5..8ed4fb8b551 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -10,14 +10,14 @@
"rollForward": false
},
"verify.tool": {
- "version": "0.8.0",
+ "version": "0.9.1",
"commands": [
"dotnet-verify"
],
"rollForward": true
},
"dotnet-trace": {
- "version": "9.0.661903",
+ "version": "10.0.731102",
"commands": [
"dotnet-trace"
],
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2d60d171643..72d8104e6be 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,6 +3,10 @@
true
+
+
+
+
@@ -11,6 +15,7 @@
+
@@ -41,29 +46,31 @@
-
+
+
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
@@ -71,6 +78,8 @@
+
+
@@ -85,6 +94,7 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -103,14 +113,14 @@
-
+
-
-
-
-
-
+
+
+
+
+
diff --git a/README.md b/README.md
index f8e829874c5..222d42adbc0 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@

-
# TUnit
@@ -17,7 +16,6 @@ A modern .NET testing framework. Tests are discovered at compile time via source
## What it looks like
-
```csharp
[Test]
[Arguments("GOLD", 100.00, 80.00)]
@@ -64,14 +62,14 @@ Source generation shifts work from run time to build time: you pay a little up f
| Scenario | TUnit (AOT) | TUnit | xUnit v3 | NUnit | MSTest |
|----------|---|---|---|---|---|
-| Data-driven tests | 13.98 ms | 268.40 ms | 586.26 ms | 498.74 ms | 490.02 ms |
-| Async-heavy tests | 118.5 ms | 358.9 ms | 737.0 ms | 577.1 ms | 678.0 ms |
-| Matrix combinations | 120.4 ms | 377.3 ms | 960.5 ms | 1,443.4 ms | 1,532.2 ms |
-| Large suites (scale) | 19.86 ms | 280.02 ms | 620.62 ms | 522.04 ms | 505.40 ms |
-| Massive parallelism | 218.2 ms | 471.8 ms | 1,289.6 ms | 1,083.0 ms | 2,975.1 ms |
-| Setup/teardown lifecycle | 70.18 ms | 389.40 ms | 784.00 ms | 1,090.23 ms | 1,163.10 ms |
-
-Mean wall-clock time to run the same test suite. TUnit (AOT) 1.65.38 · TUnit 1.65.38 · xUnit v3 4.0.0 · NUnit 4.6.1 · MSTest 4.3.3. .NET SDK 10.0.400, .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v4. Updated 2026-08-23 — regenerated weekly by the [Speed Comparison workflow](https://github.com/thomhurst/TUnit/actions/workflows/speed-comparison.yml). Full results and methodology: [tunit.dev/docs/benchmarks](https://tunit.dev/docs/benchmarks/).
+| Data-driven tests | 16.57 ms | 281.32 ms | 655.45 ms | 564.15 ms | 507.18 ms |
+| Async-heavy tests | 116.0 ms | 388.5 ms | 730.9 ms | 714.3 ms | 664.8 ms |
+| Matrix combinations | 117.1 ms | 364.9 ms | 861.9 ms | 1,536.5 ms | 1,497.2 ms |
+| Large suites (scale) | 18.88 ms | 334.54 ms | 710.91 ms | 643.81 ms | 562.39 ms |
+| Massive parallelism | 220.9 ms | 535.8 ms | 1,337.9 ms | 1,317.5 ms | 3,040.5 ms |
+| Setup/teardown lifecycle | 75.64 ms | 367.81 ms | 955.39 ms | 1,270.05 ms | 1,322.57 ms |
+
+Mean wall-clock time to run the same test suite. TUnit (AOT) 1.65.68 · TUnit 1.65.68 · xUnit v3 4.0.0 · NUnit 4.6.1 · MSTest 4.3.3. .NET SDK 10.0.400, .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v4. Updated 2026-08-30 — regenerated weekly by the [Speed Comparison workflow](https://github.com/thomhurst/TUnit/actions/workflows/speed-comparison.yml). Full results and methodology: [tunit.dev/docs/benchmarks](https://tunit.dev/docs/benchmarks/).
## Getting Started
@@ -97,7 +95,6 @@ dotnet add package TUnit
### Data-driven tests
-
```csharp
[Test]
[Arguments("user1@test.com", "ValidPassword123")]
@@ -118,7 +115,6 @@ Need more? `[MethodDataSource]` pulls rows from a method, and custom `DataSource
Assertions are async, chainable, and produce the focused failure messages shown above:
-
```csharp
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK)
.Because("the health endpoint should always be up");
@@ -161,13 +157,12 @@ public class OrderRepositoryTests
}
```
-Property injection keeps base test classes clean — subclasses inherit the fixture without re-threading constructor parameters. Prefer a constructor param? That works too. Disposal is reference-counted, so shared fixtures are torn down exactly when the last test using them finishes.
+Property injection keeps base test classes clean — subclasses inherit the fixture without re-threading constructor parameters. Prefer a constructor parameter on the test class? That works too. Types created by `ClassDataSource` require a public parameterless constructor; use property injection for their nested dependencies. Disposal is reference-counted, so shared fixtures are torn down exactly when the last test using them finishes.
### Parallelism you control
Everything runs in parallel by default. Opt out or sequence tests where it matters:
-
```csharp
[Test]
public async Task Register_User() { ... }
@@ -184,7 +179,6 @@ public async Task Migrates_Schema() { ... }
### Lifecycle hooks at every scope
-
```csharp
[Before(Test)] // also: Class, Assembly, TestSession
public async Task SetUp() { ... }
@@ -197,13 +191,13 @@ public static async Task TearDownDatabase(ClassHookContext context) { ... }
`TUnit.Mocks` is a source-generated, Native AOT-compatible mocking library — no runtime proxies, no `Castle.Core`. It works with any test framework:
-
```csharp
var gateway = IPaymentGateway.Mock(); // or Mock.Of()
gateway.ChargeAsync(Any()).Returns(new ChargeResult(Success: true));
var checkout = new CheckoutService(gateway.Object);
+var cart = new Cart(99.99m);
await checkout.CompleteAsync(cart);
gateway.ChargeAsync(99.99m).WasCalled(Times.Once);
@@ -211,7 +205,6 @@ gateway.ChargeAsync(99.99m).WasCalled(Times.Once);
Companion packages mock the annoying stuff for you:
-
```csharp
// TUnit.Mocks.Http — a real HttpClient backed by a scriptable handler
using var client = Mock.HttpClient("https://api.example.com");
@@ -219,14 +212,13 @@ client.Handler.OnGet("/users/1").RespondWithJson("""{ "id": 1 }""");
// TUnit.Mocks.Logging — capture and verify ILogger output
var logger = Mock.Logger();
-logger.VerifyLog().AtLevel(LogLevel.Warning).ContainingMessage("retrying").WasCalled(Times.Once);
+logger.VerifyLog().AtLevel(Microsoft.Extensions.Logging.LogLevel.Warning).ContainingMessage("retrying").WasCalled(Times.Once);
```
### Custom attributes
Extend built-in base classes to create your own skip conditions, retry logic, and more:
-
```csharp
public class WindowsOnlyAttribute : SkipAttribute
{
@@ -265,7 +257,6 @@ public class HealthCheckTests(ApiFactory factory)
Spin up your whole distributed app once per test session, with resource log forwarding and OpenTelemetry capture built in:
-
```csharp
public class AppFixture : AspireFixture;
@@ -302,7 +293,6 @@ public class HomePageTests : PageTest
### Property-based testing (FsCheck)
-
```csharp
[Test, FsCheckProperty]
public bool Reversing_Twice_Returns_Original(int[] array) =>
diff --git a/docs/docs/assertions/awaiting.md b/docs/docs/assertions/awaiting.md
index 0ca0c7457a5..53837f6bb5e 100644
--- a/docs/docs/assertions/awaiting.md
+++ b/docs/docs/assertions/awaiting.md
@@ -2,7 +2,6 @@
sidebar_position: 1
---
-
# Awaiting
@@ -17,7 +16,6 @@ If you forget to `await`, your assertion will not actually be executed, and your
This will error:
-
```csharp
[Test]
public void MyTest()
@@ -30,7 +28,6 @@ This will error:
This won't:
-
```csharp
[Test]
public async Task MyTest()
@@ -50,7 +47,6 @@ When you `await` an assertion in TUnit, it returns a reference to the subject th
### Type Casting with Confidence
-
```csharp
[Test]
public async Task CastAndUseSpecificType()
@@ -61,7 +57,7 @@ public async Task CastAndUseSpecificType()
var circle = await Assert.That(shape).IsTypeOf();
// Now you can use circle-specific properties without casting
- await Assert.That(circle.Radius).IsEqualTo(5.0);
+ await Assert.That(circle!.Radius).IsEqualTo(5.0);
var area = Math.PI * circle.Radius * circle.Radius;
await Assert.That(area).IsEqualTo(Math.PI * 25).Within(0.0001);
@@ -74,7 +70,6 @@ public async Task CastAndUseSpecificType()
You can chain multiple assertions together for more complex validations:
-
```csharp
[Test]
public async Task ComplexObjectValidation()
@@ -92,7 +87,6 @@ public async Task ComplexObjectValidation()
### Collection Assertions with Complex Conditions
-
```csharp
[Test]
public async Task ComplexCollectionAssertions()
@@ -116,7 +110,6 @@ public async Task ComplexCollectionAssertions()
### Async Operation Assertions
-
```csharp
[Test]
public async Task AsyncOperationAssertions()
@@ -138,7 +131,6 @@ public async Task AsyncOperationAssertions()
### Exception Assertions with Details
-
```csharp
[Test]
public async Task DetailedExceptionAssertions()
@@ -151,7 +143,7 @@ public async Task DetailedExceptionAssertions()
.WithMessage("Validation failed");
// Assert ArgumentException with parameter name
- await Assert.That(() => ProcessInvalidData(null))
+ await Assert.That(() => ProcessInvalidData((object?)null))
.Throws()
.WithParameterName("data");
@@ -159,14 +151,13 @@ public async Task DetailedExceptionAssertions()
var exception = await Assert.That(() => ParallelOperationAsync())
.Throws();
- await Assert.That(exception.InnerExceptions).Count().IsEqualTo(3);
+ await Assert.That(exception!.InnerExceptions).Count().IsEqualTo(3);
await Assert.That(exception.InnerExceptions).All(e => e is TaskCanceledException);
}
```
### Custom Assertion Conditions
-
```csharp
[Test]
public async Task CustomAssertionConditions()
@@ -176,8 +167,8 @@ public async Task CustomAssertionConditions()
// Use custom conditions for complex validations
await Assert.That(measurements)
.Satisfies(m => {
- var average = m.Average();
- var stdDev = CalculateStandardDeviation(m);
+ var average = m!.Average();
+ var stdDev = CalculateStandardDeviation(m!);
return stdDev < average * 0.1; // Less than 10% deviation
}, "Measurements should have low standard deviation");
@@ -191,7 +182,6 @@ public async Task CustomAssertionConditions()
### Combining Or and And Conditions
-
```csharp
[Test]
public async Task ComplexLogicalConditions()
diff --git a/docs/docs/assertions/boolean.md b/docs/docs/assertions/boolean.md
index f1f87a2540d..ddd3aae7d10 100644
--- a/docs/docs/assertions/boolean.md
+++ b/docs/docs/assertions/boolean.md
@@ -2,7 +2,6 @@
sidebar_position: 3.5
---
-
# Boolean Assertions
@@ -14,7 +13,6 @@ TUnit provides simple, expressive assertions for testing boolean values. These a
Tests that a boolean value is `true`:
-
```csharp
[Test]
public async Task Value_Is_True()
@@ -31,7 +29,6 @@ public async Task Value_Is_True()
Tests that a boolean value is `false`:
-
```csharp
[Test]
public async Task Value_Is_False()
@@ -39,7 +36,7 @@ public async Task Value_Is_False()
var isExpired = CheckIfExpired(futureDate);
await Assert.That(isExpired).IsFalse();
- var isEmpty = list.Count == 0;
+ var isEmpty = list.Length == 0;
await Assert.That(isEmpty).IsFalse();
}
```
@@ -48,7 +45,6 @@ public async Task Value_Is_False()
You can also use `IsEqualTo()` for boolean comparisons:
-
```csharp
[Test]
public async Task Using_IsEqualTo()
@@ -69,7 +65,6 @@ However, `IsTrue()` and `IsFalse()` are more expressive and recommended for bool
Both assertions work with nullable booleans (`bool?`):
-
```csharp
[Test]
public async Task Nullable_Boolean_True()
@@ -98,7 +93,6 @@ public async Task Nullable_Boolean_False()
If a nullable boolean is `null`, both `IsTrue()` and `IsFalse()` will fail:
-
```csharp
[Test]
public async Task Nullable_Boolean_Null()
@@ -118,7 +112,6 @@ public async Task Nullable_Boolean_Null()
Boolean assertions can be chained with other assertions:
-
```csharp
[Test]
public async Task Chained_With_Other_Assertions()
@@ -135,22 +128,20 @@ public async Task Chained_With_Other_Assertions()
### Validation Results
-
```csharp
[Test]
public async Task Email_Validation()
{
- var isValid = EmailValidator.Validate("test@example.com");
+ var isValid = ValidateEmail("test@example.com");
await Assert.That(isValid).IsTrue();
- var isInvalid = EmailValidator.Validate("not-an-email");
+ var isInvalid = ValidateEmail("not-an-email");
await Assert.That(isInvalid).IsFalse();
}
```
### Permission Checks
-
```csharp
[Test]
public async Task User_Permissions()
@@ -165,12 +156,11 @@ public async Task User_Permissions()
### State Flags
-
```csharp
[Test]
public async Task Service_State()
{
- var service = new BackgroundService();
+ var service = new ExampleBackgroundService();
await Assert.That(service.IsRunning).IsFalse();
@@ -182,7 +172,6 @@ public async Task Service_State()
### Feature Flags
-
```csharp
[Test]
public async Task Feature_Toggles()
@@ -198,7 +187,6 @@ public async Task Feature_Toggles()
When testing the boolean result of a comparison, use the specific assertion instead for clearer failure messages:
-
```csharp
[Test]
public async Task Prefer_Specific_Assertions()
diff --git a/docs/docs/assertions/collections.md b/docs/docs/assertions/collections.md
index d6636f366fe..f18b6518547 100644
--- a/docs/docs/assertions/collections.md
+++ b/docs/docs/assertions/collections.md
@@ -2,7 +2,6 @@
sidebar_position: 6.5
---
-
# Collection Assertions
@@ -14,7 +13,6 @@ TUnit provides comprehensive assertions for testing collections, including membe
Tests that a collection contains a specific item:
-
```csharp
[Test]
public async Task Collection_Contains_Item()
@@ -28,7 +26,6 @@ public async Task Collection_Contains_Item()
Works with any collection type:
-
```csharp
[Test]
public async Task Various_Collection_Types()
@@ -48,7 +45,6 @@ public async Task Various_Collection_Types()
Tests that a collection contains an item matching a predicate, and returns that item:
-
```csharp
[Test]
public async Task Collection_Contains_Matching_Item()
@@ -71,7 +67,6 @@ public async Task Collection_Contains_Matching_Item()
Tests that a collection does not contain a specific item:
-
```csharp
[Test]
public async Task Collection_Does_Not_Contain()
@@ -87,7 +82,6 @@ public async Task Collection_Does_Not_Contain()
Tests that no items match the predicate:
-
```csharp
[Test]
public async Task Collection_Does_Not_Contain_Matching()
@@ -109,7 +103,6 @@ public async Task Collection_Does_Not_Contain_Matching()
Tests that a collection has an exact count:
-
```csharp
[Test]
public async Task Collection_Has_Count()
@@ -124,7 +117,6 @@ public async Task Collection_Has_Count()
Get the count for further assertions:
-
```csharp
[Test]
public async Task Count_With_Comparison()
@@ -144,7 +136,6 @@ public async Task Count_With_Comparison()
Count items that satisfy an assertion, allowing you to reuse existing assertion methods:
-
```csharp
[Test]
public async Task Count_With_Inner_Assertion()
@@ -178,7 +169,6 @@ public async Task Count_Strings_With_Inner_Assertion()
Count assertions preserve the collection type, allowing you to chain additional collection assertions:
-
```csharp
[Test]
public async Task Count_With_Chaining()
@@ -199,9 +189,8 @@ public async Task Count_With_Chaining()
// For non-int collections, you can also use inline count assertions
var names = new[] { "Alice", "Bob", "Charlie" };
- await Assert.That(names)
- .Count(c => c.IsEqualTo(3))
- .And.Contains("Bob");
+ await Assert.That(names).Count().IsEqualTo(3);
+ await Assert.That(names).Contains("Bob");
}
```
@@ -209,7 +198,6 @@ public async Task Count_With_Chaining()
Tests that a collection has no items:
-
```csharp
[Test]
public async Task Collection_Is_Empty()
@@ -225,7 +213,6 @@ public async Task Collection_Is_Empty()
Tests that a collection has at least one item:
-
```csharp
[Test]
public async Task Collection_Is_Not_Empty()
@@ -240,7 +227,6 @@ public async Task Collection_Is_Not_Empty()
Tests that a collection has exactly one item, and returns that item:
-
```csharp
[Test]
public async Task Collection_Has_Single_Item()
@@ -255,11 +241,9 @@ public async Task Collection_Has_Single_Item()
Use `.Item` to continue assertions directly against the single item:
-
```csharp
-await Assert.That(users)
- .HasSingleItem()
- .Item.Member(user => user.Name, name => name.IsEqualTo("Alice"));
+var user = await Assert.That(users).HasSingleItem();
+await Assert.That(user.Name).IsEqualTo("Alice");
```
## Ordering Assertions
@@ -268,7 +252,6 @@ await Assert.That(users)
Tests that a collection is sorted in ascending order:
-
```csharp
[Test]
public async Task Collection_In_Ascending_Order()
@@ -279,7 +262,6 @@ public async Task Collection_In_Ascending_Order()
}
```
-
```csharp
[Test]
public async Task Strings_In_Order()
@@ -294,7 +276,6 @@ public async Task Strings_In_Order()
Tests that a collection is sorted in descending order:
-
```csharp
[Test]
public async Task Collection_In_Descending_Order()
@@ -309,7 +290,6 @@ public async Task Collection_In_Descending_Order()
Tests that a collection is ordered by a specific property:
-
```csharp
[Test]
public async Task Ordered_By_Property()
@@ -329,7 +309,6 @@ public async Task Ordered_By_Property()
Tests that a collection is ordered by a property in descending order:
-
```csharp
[Test]
public async Task Ordered_By_Descending()
@@ -351,7 +330,6 @@ public async Task Ordered_By_Descending()
Tests that all items satisfy a condition:
-
```csharp
[Test]
public async Task All_Items_Match()
@@ -366,7 +344,6 @@ public async Task All_Items_Match()
The single parameter overload will match T from `IEnumerable` - Giving you the relevant assertions for that type.
-
```csharp
[Test]
public async Task All_Satisfy_With_Property()
@@ -386,7 +363,6 @@ public async Task All_Satisfy_With_Property()
You can also map to other types by accessing properties an such - And then assert on those specific values:
-
```csharp
[Test]
public async Task All_Satisfy_With_Mapper()
@@ -410,7 +386,6 @@ public async Task All_Satisfy_With_Mapper()
Tests that at least one item satisfies a condition:
-
```csharp
[Test]
public async Task Any_Item_Matches()
@@ -429,7 +404,6 @@ Collection equivalency checks whether two collections contain the same elements.
Tests that two collections contain the same items. By default, order is ignored (use `CollectionOrdering.Matching` to require matching order):
-
```csharp
[Test]
public async Task Collections_Are_Equivalent()
@@ -443,7 +417,6 @@ public async Task Collections_Are_Equivalent()
Different collection types:
-
```csharp
[Test]
public async Task Different_Collection_Types()
@@ -457,7 +430,6 @@ public async Task Different_Collection_Types()
#### With Custom Comparer
-
```csharp
[Test]
public async Task Equivalent_With_Comparer()
@@ -473,7 +445,6 @@ public async Task Equivalent_With_Comparer()
#### With Custom Equality Predicate
-
```csharp
[Test]
public async Task Equivalent_With_Predicate()
@@ -492,7 +463,7 @@ public async Task Equivalent_With_Predicate()
await Assert.That(users1)
.IsEquivalentTo(users2)
- .Using((u1, u2) => u1.Name == u2.Name && u1.Age == u2.Age);
+ .Using((u1, u2) => u1!.Name == u2!.Name && u1.Age == u2.Age);
}
```
@@ -500,7 +471,6 @@ public async Task Equivalent_With_Predicate()
By default, `IsEquivalentTo` ignores the order of elements:
-
```csharp
[Test]
public async Task Equivalent_Ignoring_Order()
@@ -517,7 +487,6 @@ public async Task Equivalent_Ignoring_Order()
To require elements to be in the same order, pass `CollectionOrdering.Matching`:
-
```csharp
[Test]
public async Task Equivalent_With_Matching_Order()
@@ -531,7 +500,6 @@ public async Task Equivalent_With_Matching_Order()
This will fail if elements are in different positions:
-
```csharp
[Test]
public async Task Not_Equivalent_Different_Order()
@@ -548,7 +516,6 @@ public async Task Not_Equivalent_Different_Order()
Tests that collections are not equivalent:
-
```csharp
[Test]
public async Task Collections_Not_Equivalent()
@@ -570,7 +537,6 @@ The default behavior (ignoring order) is ideal for:
- Checking API responses where element order doesn't matter
- Testing collection transformations that may reorder elements
-
```csharp
[Test]
public async Task Database_Query_Results()
@@ -591,7 +557,6 @@ Use order-sensitive comparison when:
- Checking sequences where position matters
- Testing priority queues or ordered data structures
-
```csharp
[Test]
public async Task Sorted_Query_Results()
@@ -611,7 +576,6 @@ public async Task Sorted_Query_Results()
If you need multiple order-sensitive assertions in the same test, consider extracting a helper or being explicit:
-
```csharp
[Test]
public async Task Multiple_Order_Sensitive_Checks()
@@ -627,7 +591,6 @@ public async Task Multiple_Order_Sensitive_Checks()
For ordered comparisons, you can also use `IsInOrder()`:
-
```csharp
[Test]
public async Task Verify_Ordering_Separately()
@@ -646,7 +609,6 @@ public async Task Verify_Ordering_Separately()
### Deep Comparison with IsEquivalentTo
-
```csharp
[Test]
public async Task Structurally_Equal()
@@ -669,7 +631,6 @@ public async Task Structurally_Equal()
### IsNotEquivalentTo for Deep Comparison
-
```csharp
[Test]
public async Task Not_Structurally_Equal()
@@ -694,7 +655,6 @@ public async Task Not_Structurally_Equal()
Tests that all items in a collection are unique:
-
```csharp
[Test]
public async Task All_Items_Distinct()
@@ -707,7 +667,6 @@ public async Task All_Items_Distinct()
Fails if duplicates exist:
-
```csharp
[Test]
public async Task Duplicates_Fail()
@@ -723,7 +682,6 @@ public async Task Duplicates_Fail()
### Filtering Results
-
```csharp
[Test]
public async Task Filter_And_Assert()
@@ -739,7 +697,6 @@ public async Task Filter_And_Assert()
### LINQ Query Results
-
```csharp
[Test]
public async Task LINQ_Query_Results()
@@ -761,7 +718,6 @@ public async Task LINQ_Query_Results()
### Sorting Validation
-
```csharp
[Test]
public async Task Verify_Sorting()
@@ -776,7 +732,6 @@ public async Task Verify_Sorting()
### API Response Validation
-
```csharp
[Test]
public async Task API_Returns_Expected_Items()
@@ -785,14 +740,13 @@ public async Task API_Returns_Expected_Items()
await Assert.That(response)
.IsNotEmpty()
- .And.All(u => u.Id > 0)
+ .And.All(u => u.Id is int id && id > 0)
.And.All(u => !string.IsNullOrEmpty(u.Name));
}
```
### Collection Transformation
-
```csharp
[Test]
public async Task Map_And_Verify()
@@ -815,7 +769,6 @@ public async Task Map_And_Verify()
## Empty vs Null Collections
-
```csharp
[Test]
public async Task Empty_vs_Null()
@@ -833,7 +786,6 @@ public async Task Empty_vs_Null()
## Nested Collections
-
```csharp
[Test]
public async Task Nested_Collections()
@@ -856,7 +808,6 @@ public async Task Nested_Collections()
## Chaining Collection Assertions
-
```csharp
[Test]
public async Task Chained_Collection_Assertions()
@@ -878,7 +829,6 @@ public async Task Chained_Collection_Assertions()
### Materialize IEnumerable
-
```csharp
[Test]
public async Task Materialize_Before_Multiple_Assertions()
@@ -898,7 +848,6 @@ public async Task Materialize_Before_Multiple_Assertions()
## Working with HashSet and SortedSet
-
```csharp
[Test]
public async Task HashSet_Assertions()
@@ -926,7 +875,6 @@ public async Task SortedSet_Assertions()
### Validate All Items
-
```csharp
[Test]
public async Task Validate_Each_Item()
@@ -946,7 +894,6 @@ public async Task Validate_Each_Item()
Or more elegantly:
-
```csharp
[Test]
public async Task Validate_All_With_Assertion()
diff --git a/docs/docs/assertions/combining-assertions.md b/docs/docs/assertions/combining-assertions.md
index a3a598ee027..1ef0b72a709 100644
--- a/docs/docs/assertions/combining-assertions.md
+++ b/docs/docs/assertions/combining-assertions.md
@@ -2,7 +2,6 @@
sidebar_position: 11
---
-
# Combining Assertions
@@ -12,7 +11,6 @@ TUnit provides several ways to combine multiple assertions within a single test:
Use the `.And` property to chain multiple conditions on the same value. Every condition must pass for the assertion to succeed. This reads naturally and avoids repeating `Assert.That(...)` for each check.
-
```csharp
[Test]
public async Task MyTest()
@@ -20,8 +18,7 @@ public async Task MyTest()
var result = Add(1, 2);
await Assert.That(result)
- .IsNotNull()
- .And.IsPositive()
+ .IsPositive()
.And.IsEqualTo(3);
}
```
@@ -30,7 +27,6 @@ public async Task MyTest()
Use the `.Or` property when at least one condition must pass. This is useful for values that are valid across a known set of outcomes.
-
```csharp
[Test]
public async Task MyTest()
@@ -47,7 +43,6 @@ public async Task MyTest()
:::warning Mixing And/Or is not supported
`.And` and `.Or` cannot be mixed in a single chain. Attempting to use `.Or` after `.And` (or vice versa) throws `MixedAndOrAssertionsException` at runtime. If you need both kinds of logic, split the chain across multiple `Assert.That(...)` calls, or combine the conditions into a single boolean expression beforehand.
-
```csharp
// NOT supported - throws MixedAndOrAssertionsException at runtime
await Assert.That(result)
@@ -68,7 +63,6 @@ By default, a failing assertion throws immediately and stops the test. `Assert.M
Implicit scope (covers the rest of the method):
-
```csharp
[Test]
public async Task MyTest()
@@ -84,7 +78,6 @@ public async Task MyTest()
Explicit scope (covers only the block):
-
```csharp
[Test]
public async Task MyTest()
diff --git a/docs/docs/assertions/datetime.md b/docs/docs/assertions/datetime.md
index d0a2cbb3e37..c4937e93483 100644
--- a/docs/docs/assertions/datetime.md
+++ b/docs/docs/assertions/datetime.md
@@ -2,7 +2,6 @@
sidebar_position: 7.5
---
-
# DateTime and Time Assertions
@@ -12,7 +11,6 @@ TUnit provides comprehensive assertions for date and time types, including `Date
DateTime comparisons often need tolerance to account for timing variations:
-
```csharp
[Test]
public async Task DateTime_With_Tolerance()
@@ -30,7 +28,6 @@ public async Task DateTime_With_Tolerance()
### Tolerance Examples
-
```csharp
[Test]
public async Task Various_Tolerance_Values()
@@ -55,7 +52,6 @@ public async Task Various_Tolerance_Values()
Standard comparison operators work with DateTime:
-
```csharp
[Test]
public async Task DateTime_Comparison()
@@ -74,7 +70,6 @@ public async Task DateTime_Comparison()
### IsToday / IsNotToday
-
```csharp
[Test]
public async Task DateTime_Is_Today()
@@ -92,7 +87,6 @@ public async Task DateTime_Is_Today()
### IsUtc / IsNotUtc
-
```csharp
[Test]
public async Task DateTime_Kind()
@@ -110,7 +104,6 @@ public async Task DateTime_Kind()
### IsLeapYear / IsNotLeapYear
-
```csharp
[Test]
public async Task Leap_Year_Check()
@@ -127,7 +120,6 @@ public async Task Leap_Year_Check()
Compares against local time:
-
```csharp
[Test]
public async Task Future_and_Past()
@@ -144,7 +136,6 @@ public async Task Future_and_Past()
Compares against UTC time:
-
```csharp
[Test]
public async Task Future_and_Past_UTC()
@@ -159,7 +150,6 @@ public async Task Future_and_Past_UTC()
### IsOnWeekend / IsOnWeekday
-
```csharp
[Test]
public async Task Weekend_Check()
@@ -169,13 +159,12 @@ public async Task Weekend_Check()
var monday = new DateTime(2024, 1, 8); // Monday
await Assert.That(monday).IsOnWeekday();
- await Assert.That(monday).IsNotOnWeekend();
+ await Assert.That(monday.DayOfWeek is not DayOfWeek.Saturday and not DayOfWeek.Sunday).IsTrue();
}
```
### IsDaylightSavingTime / IsNotDaylightSavingTime
-
```csharp
[Test]
public async Task Daylight_Saving_Time()
@@ -195,7 +184,6 @@ public async Task Daylight_Saving_Time()
DateTimeOffset includes timezone information:
-
```csharp
[Test]
public async Task DateTimeOffset_With_Tolerance()
@@ -207,7 +195,6 @@ public async Task DateTimeOffset_With_Tolerance()
}
```
-
```csharp
[Test]
public async Task DateTimeOffset_Comparison()
@@ -224,7 +211,6 @@ public async Task DateTimeOffset_Comparison()
DateOnly represents just a date without time:
-
```csharp
[Test]
public async Task DateOnly_Assertions()
@@ -238,7 +224,6 @@ public async Task DateOnly_Assertions()
### DateOnly with Days Tolerance
-
```csharp
[Test]
public async Task DateOnly_With_Tolerance()
@@ -252,7 +237,6 @@ public async Task DateOnly_With_Tolerance()
### DateOnly Comparison
-
```csharp
[Test]
public async Task DateOnly_Comparison()
@@ -269,7 +253,6 @@ public async Task DateOnly_Comparison()
TimeOnly represents just time without a date:
-
```csharp
[Test]
public async Task TimeOnly_Assertions()
@@ -283,7 +266,6 @@ public async Task TimeOnly_Assertions()
### TimeOnly with Tolerance
-
```csharp
[Test]
public async Task TimeOnly_With_Tolerance()
@@ -299,7 +281,6 @@ public async Task TimeOnly_With_Tolerance()
TimeSpan represents a duration:
-
```csharp
[Test]
public async Task TimeSpan_Assertions()
@@ -313,7 +294,6 @@ public async Task TimeSpan_Assertions()
### TimeSpan Comparison
-
```csharp
[Test]
public async Task TimeSpan_Comparison()
@@ -328,7 +308,6 @@ public async Task TimeSpan_Comparison()
### TimeSpan Sign Checks
-
```csharp
[Test]
public async Task TimeSpan_Sign()
@@ -345,7 +324,6 @@ public async Task TimeSpan_Sign()
### Expiration Checks
-
```csharp
[Test]
public async Task Check_Token_Expiration()
@@ -363,7 +341,6 @@ public async Task Check_Token_Expiration()
### Age Calculation
-
```csharp
[Test]
public async Task Calculate_Age()
@@ -383,7 +360,6 @@ public async Task Calculate_Age()
### Business Days
-
```csharp
[Test]
public async Task Is_Business_Day()
@@ -398,7 +374,6 @@ public async Task Is_Business_Day()
### Scheduling
-
```csharp
[Test]
public async Task Scheduled_Time()
@@ -413,7 +388,6 @@ public async Task Scheduled_Time()
### Performance Timing
-
```csharp
[Test]
public async Task Operation_Duration()
@@ -431,7 +405,6 @@ public async Task Operation_Duration()
### Date Range Validation
-
```csharp
[Test]
public async Task Date_Within_Range()
@@ -447,7 +420,6 @@ public async Task Date_Within_Range()
### Timestamp Validation
-
```csharp
[Test]
public async Task Record_Created_Recently()
@@ -464,7 +436,6 @@ public async Task Record_Created_Recently()
## Working with Date Components
-
```csharp
[Test]
public async Task Date_Components()
@@ -484,7 +455,6 @@ public async Task Date_Components()
DayOfWeek has its own assertions:
-
```csharp
[Test]
public async Task Day_Of_Week_Checks()
@@ -504,7 +474,6 @@ public async Task Day_Of_Week_Checks()
## Chaining DateTime Assertions
-
```csharp
[Test]
public async Task Chained_DateTime_Assertions()
@@ -522,7 +491,6 @@ public async Task Chained_DateTime_Assertions()
### Birthday Validation
-
```csharp
[Test]
public async Task Validate_Birthday()
@@ -536,7 +504,6 @@ public async Task Validate_Birthday()
### Meeting Scheduler
-
```csharp
[Test]
public async Task Schedule_Meeting()
@@ -551,7 +518,6 @@ public async Task Schedule_Meeting()
### Relative Time Checks
-
```csharp
[Test]
public async Task Within_Last_Hour()
diff --git a/docs/docs/assertions/dictionaries.md b/docs/docs/assertions/dictionaries.md
index 9ee0ed982a4..e4203c11cde 100644
--- a/docs/docs/assertions/dictionaries.md
+++ b/docs/docs/assertions/dictionaries.md
@@ -2,7 +2,6 @@
sidebar_position: 6.8
---
-
# Dictionary Assertions
@@ -14,7 +13,6 @@ TUnit provides specialized assertions for testing dictionaries (`IReadOnlyDictio
Tests that a dictionary contains a specific key:
-
```csharp
[Test]
public async Task Dictionary_Contains_Key()
@@ -33,7 +31,6 @@ public async Task Dictionary_Contains_Key()
#### With Custom Comparer
-
```csharp
[Test]
public async Task Contains_Key_With_Comparer()
@@ -54,7 +51,6 @@ public async Task Contains_Key_With_Comparer()
Tests that a dictionary does not contain a specific key:
-
```csharp
[Test]
public async Task Dictionary_Does_Not_Contain_Key()
@@ -76,7 +72,6 @@ public async Task Dictionary_Does_Not_Contain_Key()
Tests that a dictionary contains a specific value:
-
```csharp
[Test]
public async Task Dictionary_Contains_Value()
@@ -99,7 +94,6 @@ Dictionaries inherit all collection assertions since they implement `IEnumerable
### Count
-
```csharp
[Test]
public async Task Dictionary_Count()
@@ -117,7 +111,6 @@ public async Task Dictionary_Count()
### IsEmpty / IsNotEmpty
-
```csharp
[Test]
public async Task Dictionary_Empty()
@@ -132,7 +125,6 @@ public async Task Dictionary_Empty()
### Contains (KeyValuePair)
-
```csharp
[Test]
public async Task Dictionary_Contains_Pair()
@@ -149,7 +141,6 @@ public async Task Dictionary_Contains_Pair()
### All Pairs Match Condition
-
```csharp
[Test]
public async Task All_Values_Positive()
@@ -167,7 +158,6 @@ public async Task All_Values_Positive()
### Any Pair Matches Condition
-
```csharp
[Test]
public async Task Any_Key_Starts_With()
@@ -187,12 +177,11 @@ public async Task Any_Key_Starts_With()
### Configuration Validation
-
```csharp
[Test]
public async Task Configuration_Has_Required_Keys()
{
- var config = LoadConfiguration();
+ var config = GetConfigurationValues();
using (Assert.Multiple())
{
@@ -205,7 +194,6 @@ public async Task Configuration_Has_Required_Keys()
### HTTP Headers Validation
-
```csharp
[Test]
public async Task Response_Headers()
@@ -224,7 +212,6 @@ public async Task Response_Headers()
### Lookup Table Validation
-
```csharp
[Test]
public async Task Lookup_Table()
@@ -245,7 +232,6 @@ public async Task Lookup_Table()
### Cache Validation
-
```csharp
[Test]
public async Task Cache_Contains_Entry()
@@ -267,7 +253,6 @@ public async Task Cache_Contains_Entry()
### Accessing Values After Key Check
-
```csharp
[Test]
public async Task Get_Value_After_Key_Check()
@@ -288,7 +273,6 @@ public async Task Get_Value_After_Key_Check()
### TryGetValue Pattern
-
```csharp
[Test]
public async Task TryGetValue_Pattern()
@@ -309,7 +293,6 @@ public async Task TryGetValue_Pattern()
### Keys Collection
-
```csharp
[Test]
public async Task Dictionary_Keys()
@@ -333,7 +316,6 @@ public async Task Dictionary_Keys()
### Values Collection
-
```csharp
[Test]
public async Task Dictionary_Values()
@@ -359,7 +341,6 @@ public async Task Dictionary_Values()
### Same Key-Value Pairs
-
```csharp
[Test]
public async Task Dictionaries_Are_Equivalent()
@@ -383,7 +364,6 @@ public async Task Dictionaries_Are_Equivalent()
## Chaining Dictionary Assertions
-
```csharp
[Test]
public async Task Chained_Dictionary_Assertions()
@@ -409,7 +389,6 @@ public async Task Chained_Dictionary_Assertions()
### ConcurrentDictionary
-
```csharp
[Test]
public async Task Concurrent_Dictionary()
@@ -426,7 +405,6 @@ public async Task Concurrent_Dictionary()
### ReadOnlyDictionary
-
```csharp
[Test]
public async Task ReadOnly_Dictionary()
@@ -442,7 +420,6 @@ public async Task ReadOnly_Dictionary()
### SortedDictionary
-
```csharp
[Test]
public async Task Sorted_Dictionary()
@@ -464,7 +441,6 @@ public async Task Sorted_Dictionary()
### Null Dictionary
-
```csharp
[Test]
public async Task Null_Dictionary()
@@ -477,7 +453,6 @@ public async Task Null_Dictionary()
### Empty vs Null
-
```csharp
[Test]
public async Task Empty_vs_Null_Dictionary()
@@ -495,12 +470,11 @@ public async Task Empty_vs_Null_Dictionary()
### Required Configuration Keys
-
```csharp
[Test]
public async Task All_Required_Keys_Present()
{
- var config = LoadConfiguration();
+ var config = GetConfigurationValues();
var requiredKeys = new[] { "ApiKey", "Database", "Environment" };
foreach (var key in requiredKeys)
@@ -512,12 +486,11 @@ public async Task All_Required_Keys_Present()
Or with `Assert.Multiple`:
-
```csharp
[Test]
public async Task All_Required_Keys_Present_Multiple()
{
- var config = LoadConfiguration();
+ var config = GetConfigurationValues();
var requiredKeys = new[] { "ApiKey", "Database", "Environment" };
using (Assert.Multiple())
@@ -532,7 +505,6 @@ public async Task All_Required_Keys_Present_Multiple()
### Metadata Validation
-
```csharp
[Test]
public async Task Validate_Metadata()
@@ -549,7 +521,6 @@ public async Task Validate_Metadata()
### Feature Flags
-
```csharp
[Test]
public async Task Feature_Flags()
diff --git a/docs/docs/assertions/equality-and-comparison.md b/docs/docs/assertions/equality-and-comparison.md
index 9025e320700..3ffee49074d 100644
--- a/docs/docs/assertions/equality-and-comparison.md
+++ b/docs/docs/assertions/equality-and-comparison.md
@@ -2,7 +2,6 @@
sidebar_position: 2
---
-
# Equality and Comparison Assertions
@@ -14,7 +13,6 @@ TUnit provides comprehensive assertions for testing equality and comparing value
Tests that two values are equal using the type's `Equals()` method or `==` operator:
-
```csharp
[Test]
public async Task Basic_Equality()
@@ -34,7 +32,6 @@ public async Task Basic_Equality()
Tests that two values are not equal:
-
```csharp
[Test]
public async Task Not_Equal()
@@ -53,7 +50,6 @@ public async Task Not_Equal()
Tests that two references point to the exact same object instance:
-
```csharp
[Test]
public async Task Same_Reference()
@@ -69,7 +65,6 @@ public async Task Same_Reference()
Tests that two references point to different object instances:
-
```csharp
[Test]
public async Task Different_References()
@@ -89,7 +84,6 @@ All comparison assertions work with types that implement `IComparable` or `IC
### IsGreaterThan
-
```csharp
[Test]
public async Task Greater_Than()
@@ -107,7 +101,6 @@ public async Task Greater_Than()
### IsGreaterThanOrEqualTo
-
```csharp
[Test]
public async Task Greater_Than_Or_Equal()
@@ -122,7 +115,6 @@ public async Task Greater_Than_Or_Equal()
### IsLessThan
-
```csharp
[Test]
public async Task Less_Than()
@@ -137,7 +129,6 @@ public async Task Less_Than()
### IsLessThanOrEqualTo
-
```csharp
[Test]
public async Task Less_Than_Or_Equal()
@@ -154,7 +145,6 @@ public async Task Less_Than_Or_Equal()
Tests that a value falls within a range (inclusive):
-
```csharp
[Test]
public async Task Between_Values()
@@ -172,7 +162,6 @@ public async Task Between_Values()
Boundary values are included:
-
```csharp
[Test]
public async Task Between_Includes_Boundaries()
@@ -189,7 +178,6 @@ public async Task Between_Includes_Boundaries()
Tests that a numeric value is greater than zero:
-
```csharp
[Test]
public async Task Positive_Numbers()
@@ -214,7 +202,6 @@ public async Task Positive_Numbers()
Tests that a numeric value is less than zero:
-
```csharp
[Test]
public async Task Negative_Numbers()
@@ -233,7 +220,6 @@ When comparing floating-point numbers, you can specify a tolerance to account fo
### Double Tolerance
-
```csharp
[Test]
public async Task Double_With_Tolerance()
@@ -251,7 +237,6 @@ public async Task Double_With_Tolerance()
### Float Tolerance
-
```csharp
[Test]
public async Task Float_With_Tolerance()
@@ -265,7 +250,6 @@ public async Task Float_With_Tolerance()
### Decimal Tolerance
-
```csharp
[Test]
public async Task Decimal_With_Tolerance()
@@ -279,7 +263,6 @@ public async Task Decimal_With_Tolerance()
### Long Tolerance
-
```csharp
[Test]
public async Task Long_With_Tolerance()
@@ -297,7 +280,6 @@ public async Task Long_With_Tolerance()
Combine multiple comparison assertions:
-
```csharp
[Test]
public async Task Chained_Comparisons()
@@ -313,7 +295,6 @@ public async Task Chained_Comparisons()
Or use `IsBetween` for simpler range checks:
-
```csharp
[Test]
public async Task Range_Check_Simplified()
@@ -329,7 +310,6 @@ public async Task Range_Check_Simplified()
You can provide custom equality comparers for collections and complex types:
-
```csharp
[Test]
public async Task Custom_Comparer()
@@ -357,7 +337,6 @@ public class PersonNameComparer : IEqualityComparer
Or use a predicate:
-
```csharp
[Test]
public async Task Custom_Equality_Predicate()
@@ -367,7 +346,7 @@ public async Task Custom_Equality_Predicate()
await Assert.That(people1)
.IsEquivalentTo(people2)
- .Using((p1, p2) => string.Equals(p1.Name, p2.Name,
+ .Using((p1, p2) => string.Equals(p1!.Name, p2!.Name,
StringComparison.OrdinalIgnoreCase));
}
```
@@ -376,7 +355,6 @@ public async Task Custom_Equality_Predicate()
Equality works naturally with value types and records:
-
```csharp
public record Point(int X, int Y);
@@ -392,7 +370,6 @@ public async Task Record_Equality()
}
```
-
```csharp
public struct Coordinate
{
diff --git a/docs/docs/assertions/exceptions.md b/docs/docs/assertions/exceptions.md
index a1b86baa610..2a949bdf31d 100644
--- a/docs/docs/assertions/exceptions.md
+++ b/docs/docs/assertions/exceptions.md
@@ -2,7 +2,6 @@
sidebar_position: 8
---
-
# Exception Assertions
@@ -14,7 +13,6 @@ TUnit provides comprehensive assertions for testing that code throws (or doesn't
Tests that a delegate throws a specific exception type (or a subclass):
-
```csharp
[Test]
public async Task Code_Throws_Exception()
@@ -26,7 +24,6 @@ public async Task Code_Throws_Exception()
Works with any exception type:
-
```csharp
[Test]
public async Task Various_Exception_Types()
@@ -46,7 +43,6 @@ public async Task Various_Exception_Types()
Tests that a delegate throws the exact exception type (not a subclass):
-
```csharp
[Test]
public async Task Throws_Exact_Type()
@@ -64,7 +60,6 @@ public async Task Throws_Exact_Type()
Use when the exception type is only known at runtime. On a synchronous delegate, fluent chaining only supports the generic `Throws()` / `ThrowsExactly()` forms, so reach for the static `Assert.Throws(Type, Action)` helper instead. On an async delegate you can call `ThrowsAsync(Type)` directly.
-
```csharp
[Test]
public async Task Throws_Runtime_Type_Sync()
@@ -92,7 +87,6 @@ public async Task Throws_Runtime_Type_Async()
Tests that code does not throw any exception:
-
```csharp
[Test]
public async Task Code_Does_Not_Throw()
@@ -109,7 +103,6 @@ public async Task Code_Does_Not_Throw()
For async operations, use async delegates:
-
```csharp
[Test]
public async Task Async_Throws_Exception()
@@ -119,7 +112,6 @@ public async Task Async_Throws_Exception()
}
```
-
```csharp
[Test]
public async Task Async_Does_Not_Throw()
@@ -135,7 +127,6 @@ public async Task Async_Does_Not_Throw()
Tests that the exception has an exact message:
-
```csharp
[Test]
public async Task Exception_With_Exact_Message()
@@ -150,7 +141,6 @@ public async Task Exception_With_Exact_Message()
Tests that the exception message contains a substring:
-
```csharp
[Test]
public async Task Exception_Message_Contains()
@@ -163,15 +153,13 @@ public async Task Exception_Message_Contains()
#### Case-Insensitive
-
```csharp
[Test]
public async Task Message_Contains_Ignoring_Case()
{
await Assert.That(() => throw new Exception("ERROR: Failed"))
.Throws()
- .WithMessageContaining("error")
- .IgnoringCase();
+ .WithMessageContaining("error", StringComparison.OrdinalIgnoreCase);
}
```
@@ -179,7 +167,6 @@ public async Task Message_Contains_Ignoring_Case()
Tests that the exception message does not contain a substring:
-
```csharp
[Test]
public async Task Message_Does_Not_Contain()
@@ -194,7 +181,6 @@ public async Task Message_Does_Not_Contain()
Tests that the exception message matches a pattern:
-
```csharp
[Test]
public async Task Message_Matches_Pattern()
@@ -207,12 +193,11 @@ public async Task Message_Matches_Pattern()
Or with a `StringMatcher`:
-
```csharp
[Test]
public async Task Message_Matches_With_Matcher()
{
- var matcher = new StringMatcher("Error * occurred", caseSensitive: false);
+ var matcher = StringMatcher.AsWildcard("Error * occurred").IgnoringCase();
await Assert.That(() => throw new Exception("Error 500 occurred"))
.Throws()
@@ -226,7 +211,6 @@ public async Task Message_Matches_With_Matcher()
For `ArgumentException` and its subclasses, you can assert on the parameter name:
-
```csharp
[Test]
public async Task ArgumentException_With_Parameter_Name()
@@ -245,15 +229,14 @@ void ValidateUser(User user)
Combine with message assertions:
-
```csharp
[Test]
public async Task ArgumentException_Parameter_And_Message()
{
- await Assert.That(() => SetAge(-1))
- .Throws()
- .WithParameterName("age")
- .WithMessageContaining("must be positive");
+ var exception = await Assert.That(() => SetAge(-1))
+ .Throws();
+ await Assert.That(exception!.ParamName).IsEqualTo("age");
+ await Assert.That(exception.Message).Contains("must be positive");
}
void SetAge(int age)
@@ -269,7 +252,6 @@ void SetAge(int age)
Assert on the inner exception:
-
```csharp
[Test]
public async Task Exception_With_Inner_Exception()
@@ -291,15 +273,13 @@ public async Task Exception_With_Inner_Exception()
Chain to assert on the inner exception type:
-
```csharp
[Test]
public async Task Inner_Exception_Type()
{
await Assert.That(() => ThrowWithInner())
.Throws()
- .WithInnerException()
- .Throws();
+ .WithInnerException();
}
void ThrowWithInner()
@@ -319,26 +299,24 @@ void ThrowWithInner()
### Validation Exceptions
-
```csharp
[Test]
public async Task Validate_Email_Throws()
{
- await Assert.That(() => ValidateEmail("invalid-email"))
- .Throws()
- .WithParameterName("email")
- .WithMessageContaining("valid email");
+ var exception = await Assert.That(() => ValidateEmail("invalid-email"))
+ .Throws();
+ await Assert.That(exception!.ParamName).IsEqualTo("email");
+ await Assert.That(exception.Message).Contains("valid email");
}
```
### Null Argument Checks
-
```csharp
[Test]
public async Task Null_Argument_Throws()
{
- await Assert.That(() => ProcessData(null!))
+ await Assert.That(() => ProcessData((object?) null))
.Throws()
.WithParameterName("data");
}
@@ -346,7 +324,6 @@ public async Task Null_Argument_Throws()
### File Operations
-
```csharp
[Test]
public async Task File_Not_Found()
@@ -359,7 +336,6 @@ public async Task File_Not_Found()
### Network Operations
-
```csharp
[Test]
public async Task HTTP_Request_Fails()
@@ -371,7 +347,6 @@ public async Task HTTP_Request_Fails()
### Database Operations
-
```csharp
[Test]
public async Task Duplicate_Key_Violation()
@@ -384,7 +359,6 @@ public async Task Duplicate_Key_Violation()
### Division by Zero
-
```csharp
[Test]
public async Task Division_By_Zero()
@@ -400,7 +374,6 @@ public async Task Division_By_Zero()
### Index Out of Range
-
```csharp
[Test]
public async Task Array_Index_Out_Of_Range()
@@ -414,7 +387,6 @@ public async Task Array_Index_Out_Of_Range()
### Invalid Cast
-
```csharp
[Test]
public async Task Invalid_Cast()
@@ -428,7 +400,6 @@ public async Task Invalid_Cast()
### Custom Exceptions
-
```csharp
public class BusinessRuleException : Exception
{
@@ -449,7 +420,7 @@ public async Task Custom_Exception_With_Properties()
.Throws();
// Can't directly assert on exception properties yet, but you can access them
- await Assert.That(exception.RuleCode).IsEqualTo("BR001");
+ await Assert.That(exception!.RuleCode).IsEqualTo("BR001");
await Assert.That(exception.Message).Contains("Business rule");
}
```
@@ -458,7 +429,6 @@ public async Task Custom_Exception_With_Properties()
### Using Assert.Multiple
-
```csharp
[Test]
public async Task Multiple_Exception_Scenarios()
@@ -481,7 +451,6 @@ public async Task Multiple_Exception_Scenarios()
When using `Throws()`, subclasses are accepted:
-
```csharp
[Test]
public async Task Exception_Inheritance()
@@ -497,7 +466,6 @@ public async Task Exception_Inheritance()
Use `ThrowsExactly()` if you need the exact type:
-
```csharp
[Test]
public async Task Exact_Exception_Type()
@@ -513,7 +481,6 @@ public async Task Exact_Exception_Type()
## Aggregate Exceptions
-
```csharp
[Test]
public async Task Aggregate_Exception()
@@ -529,16 +496,15 @@ public async Task Aggregate_Exception()
## Chaining Exception Assertions
-
```csharp
[Test]
public async Task Chained_Exception_Assertions()
{
- await Assert.That(() => ValidateInput(""))
- .Throws()
- .WithParameterName("input")
- .WithMessageContaining("cannot be empty")
- .WithMessageNotContaining("null");
+ var exception = await Assert.That(() => ValidateInput(""))
+ .Throws();
+ await Assert.That(exception!.ParamName).IsEqualTo("input");
+ await Assert.That(exception.Message).Contains("cannot be empty");
+ await Assert.That(exception.Message).DoesNotContain("null");
}
```
@@ -546,7 +512,6 @@ public async Task Chained_Exception_Assertions()
### ThrowsNothing vs Try-Catch
-
```csharp
[Test]
public async Task Explicit_No_Exception()
@@ -564,7 +529,6 @@ public async Task Explicit_No_Exception()
### Expected Failures
-
```csharp
[Test]
public async Task Expected_Validation_Failure()
@@ -579,7 +543,6 @@ public async Task Expected_Validation_Failure()
### Defensive Programming
-
```csharp
[Test]
public async Task Guard_Clause_Validation()
@@ -592,7 +555,6 @@ public async Task Guard_Clause_Validation()
### State Validation
-
```csharp
[Test]
public async Task Invalid_State_Operation()
@@ -608,7 +570,6 @@ public async Task Invalid_State_Operation()
### Configuration Errors
-
```csharp
[Test]
public async Task Missing_Configuration()
@@ -621,7 +582,6 @@ public async Task Missing_Configuration()
## Timeout Exceptions
-
```csharp
[Test]
public async Task Operation_Timeout()
@@ -635,7 +595,6 @@ public async Task Operation_Timeout()
## Re-throwing Exceptions
-
```csharp
[Test]
public async Task Wrapper_Exception()
@@ -657,7 +616,6 @@ public async Task Wrapper_Exception()
## Exception Assertions with Async/Await
-
```csharp
[Test]
public async Task Async_Exception_Handling()
diff --git a/docs/docs/assertions/extensibility/custom-assertions.md b/docs/docs/assertions/extensibility/custom-assertions.md
index 912397912ad..1e47f60dd5e 100644
--- a/docs/docs/assertions/extensibility/custom-assertions.md
+++ b/docs/docs/assertions/extensibility/custom-assertions.md
@@ -2,7 +2,6 @@
sidebar_position: 1
---
-
# Custom Assertions
@@ -110,3 +109,4 @@ await Assert.That("Hello World")
- **Context sharing**: Pass `source.Context` to your assertion constructor (it contains the evaluation context and expression builder)
- **CheckAsync parameter**: Use `EvaluationMetadata metadata` which contains both `Value` and `Exception` properties
- **CallerArgumentExpression**: Use this attribute to capture parameter expressions for better error messages
+
diff --git a/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md b/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md
index e92cf8bb465..ddc6a1cb7b6 100644
--- a/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md
+++ b/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md
@@ -2,7 +2,6 @@
sidebar_position: 3
---
-
# Chaining and Converting
@@ -13,16 +12,22 @@ Chaining is especially helpful when you want to perform multiple assertions on a
For example:
-
```csharp
- HttpResponseMessage response = ...;
-
- await Assert.That(response)
- .IsProblemDetails()
- .And
- .HasTitle("Invalid Authentication Token")
- .And
- .HasDetail("No token provided");
+using var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
+{
+ Content = JsonContent.Create(new ProblemDetails
+ {
+ Title = "Invalid Authentication Token",
+ Detail = "No token provided"
+ })
+};
+
+await Assert.That(response)
+ .IsProblemDetails()
+ .And
+ .HasTitle("Invalid Authentication Token")
+ .And
+ .HasDetail("No token provided");
```
The `response` object initially passed in is a `HttpResponseMessage`, but then after we assert it's a `ProblemDetails` object, the chain has changed to that type so that we can further assert with methods specific to `ProblemDetails` instead of `HttpResponseMessage`.
@@ -43,7 +48,7 @@ public class IsProblemDetailsAssertion : Assertion
public IsProblemDetailsAssertion(AssertionContext context)
: base(context.Map(async response =>
{
- var content = await response.Content.ReadFromJsonAsync();
+ var content = await response!.Content.ReadFromJsonAsync();
if (content is null)
{
@@ -193,3 +198,4 @@ TUnit includes several built-in examples of type conversion assertions:
- `WhenParsedInto()` - Converts a string to a parsed type (e.g., `await Assert.That("123").WhenParsedInto().IsEqualTo(123)`)
- `IsTypeOf()` - Converts to a specific type (e.g., `await Assert.That(obj).IsTypeOf().Length().IsEqualTo(5)`)
+
diff --git a/docs/docs/assertions/extensibility/extensibility-returning-items-from-await.md b/docs/docs/assertions/extensibility/extensibility-returning-items-from-await.md
index 64649277267..a426f9754f5 100644
--- a/docs/docs/assertions/extensibility/extensibility-returning-items-from-await.md
+++ b/docs/docs/assertions/extensibility/extensibility-returning-items-from-await.md
@@ -2,7 +2,6 @@
sidebar_position: 4
---
-
# Returning Data via `await`
@@ -101,7 +100,7 @@ You can now use the assertion and get the found item:
```csharp
// Returns the first item with price < 0.99
-Product cheapProduct = await Assert.That(products).Contains(p => p.Price < 0.99);
+Product cheapProduct = await Assert.That(products).Contains(p => p.Price < 0.99m);
// Use the returned value in further assertions
await Assert.That(cheapProduct.Name).IsNotNull();
@@ -115,3 +114,4 @@ TUnit includes several built-in examples of assertions that return values:
- `Contains(predicate)` - Returns the first item matching the predicate
- `WhenParsedInto()` - Returns the parsed value (e.g., `int value = await Assert.That("123").WhenParsedInto()`)
- `IsTypeOf()` - Returns the casted value (e.g., `StringBuilder sb = await Assert.That(obj).IsTypeOf()`)
+
diff --git a/docs/docs/assertions/extensibility/source-generator-assertions.md b/docs/docs/assertions/extensibility/source-generator-assertions.md
index 99850dedbdd..6cb1658dc8b 100644
--- a/docs/docs/assertions/extensibility/source-generator-assertions.md
+++ b/docs/docs/assertions/extensibility/source-generator-assertions.md
@@ -2,7 +2,6 @@
sidebar_position: 2
---
-
# Source Generator Assertions
@@ -52,7 +51,7 @@ The generator creates:
2. An extension method on `IAssertionSource`
3. Full support for chaining with `.And` and `.Or`
-```csharp
+```text
// Generated code (simplified):
public sealed class IsPositive_Assertion : Assertion
{
@@ -116,14 +115,14 @@ public static bool IsBetween(this int value, int min, int max)
```csharp
[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to be even")]
-public static bool IsEven(this int value)
+public static bool HasEvenValue(this int value)
{
return value % 2 == 0;
}
// Usage:
-await Assert.That(4).IsEven(); // ✅ Passes
-await Assert.That(3).IsEven(); // ❌ Fails: "Expected to be even but found 3"
+await Assert.That(4).HasEvenValue(); // ✅ Passes
+await Assert.That(3).HasEvenValue(); // ❌ Fails: "Expected to be even but found 3"
```
### 2. `AssertionResult` - Custom Messages
@@ -157,12 +156,14 @@ await Assert.That(15).IsPrime(); // ❌ Fails: "Expected to be prime but 15 is
```csharp
[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to exist in database")]
-public static async Task ExistsInDatabaseAsync(this int userId, DbContext db)
+public static async Task ExistsInDatabaseAsync(this int userId, ApplicationDbContext db)
{
- return await db.Users.AnyAsync(u => u.Id == userId);
+ return await db.Users.AnyAsync(u => Equals(u.Id, userId));
}
// Usage:
+var userId = 123;
+await using var dbContext = new ApplicationDbContext();
await Assert.That(userId).ExistsInDatabaseAsync(dbContext);
// If fails: "Expected to exist in database but found 123"
```
@@ -172,7 +173,7 @@ await Assert.That(userId).ExistsInDatabaseAsync(dbContext);
```csharp
[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to have valid email")]
-public static async Task HasValidEmailAsync(this int userId, DbContext db)
+public static async Task HasValidEmailAsync(this int userId, ApplicationDbContext db)
{
var user = await db.Users.FindAsync(userId);
@@ -186,6 +187,7 @@ public static async Task HasValidEmailAsync(this int userId, Db
}
// Usage:
+await using var dbContext = new ApplicationDbContext();
await Assert.That(123).HasValidEmailAsync(dbContext);
// If fails: "Expected to have valid email but User 123 not found"
```
@@ -236,18 +238,18 @@ Use `[AssertionFrom]` to create assertions from existing methods in libraries or
```csharp
using TUnit.Assertions.Attributes;
-[AssertionFrom(nameof(string.IsNullOrEmpty), ExpectationMessage = "to be null or empty")]
-[AssertionFrom(nameof(string.StartsWith), ExpectationMessage = "to start with {value}")]
-[AssertionFrom(nameof(string.EndsWith), ExpectationMessage = "to end with {value}")]
+[AssertionFrom(nameof(string.IsNullOrEmpty), CustomName = "IsBlank", ExpectationMessage = "to be null or empty")]
+[AssertionFrom(nameof(string.StartsWith), CustomName = "BeginsWithText", ExpectationMessage = "to start with {value}")]
+[AssertionFrom(nameof(string.EndsWith), CustomName = "EndsWithText", ExpectationMessage = "to end with {value}")]
public static partial class StringAssertionExtensions
{
}
// Usage:
-await Assert.That(myString).IsNullOrEmpty();
+await Assert.That("").IsBlank();
// If fails: "Expected to be null or empty but found 'test'"
-await Assert.That("hello").StartsWith("he");
+await Assert.That("hello").BeginsWithText("he");
// If fails: "Expected to start with 'he' but found 'hello'"
```
@@ -269,14 +271,14 @@ await Assert.That("hello").Has("world"); // ❌ Fails: "Expected to have
For `bool`-returning methods, you can generate negated versions:
```csharp
-[AssertionFrom(nameof(string.Contains), CustomName = "DoesNotContain", NegateLogic = true, ExpectationMessage = "to not contain '{value}'")]
+[AssertionFrom(nameof(string.Contains), CustomName = "LacksText", NegateLogic = true, ExpectationMessage = "to not contain '{value}'")]
public static partial class StringAssertionExtensions
{
}
// Usage:
-await Assert.That("hello").DoesNotContain("xyz"); // ✅ Passes
-await Assert.That("hello").DoesNotContain("ell"); // ❌ Fails: "Expected to not contain 'ell' but found 'hello'"
+await Assert.That("hello").LacksText("xyz"); // ✅ Passes
+await Assert.That("hello").LacksText("ell"); // ❌ Fails: "Expected to not contain 'ell' but found 'hello'"
```
**Note:** Negation only works with `bool`-returning methods. `AssertionResult` methods determine their own pass/fail logic.
@@ -321,21 +323,22 @@ using TUnit.Assertions.Attributes;
file static class BoolAssertions
{
[GenerateAssertion(ExpectationMessage = "to be true", InlineMethodBody = true)]
- public static bool IsTrue(this bool value) => value == true;
+ public static bool HasTrueValue(this bool value) => value == true;
[GenerateAssertion(ExpectationMessage = "to be false", InlineMethodBody = true)]
- public static bool IsFalse(this bool value) => value == false;
+ public static bool HasFalseValue(this bool value) => value == false;
}
// Usage in tests:
-await Assert.That(myBool).IsTrue(); // ✅ Clean API, no IntelliSense pollution
+var myBool = true;
+await Assert.That(myBool).HasTrueValue(); // ✅ Clean API, no IntelliSense pollution
```
### What Gets Generated with Inlining
Instead of calling your method, the generator inlines the expression directly:
-```csharp
+```text
// WITHOUT InlineMethodBody (calls the method):
protected override Task CheckAsync(EvaluationMetadata metadata)
{
@@ -417,7 +420,7 @@ public static partial class StringAssertionExtensions
// ❌ BAD: Will appear in IntelliSense when typing on string values
[GenerateAssertion]
- public static bool IsEmptyString(this string value) => value.Length == 0;
+ public static bool IsEmptyStringVisible(this string value) => value.Length == 0;
}
```
@@ -455,16 +458,17 @@ public static bool IsPositive(this int value) => value > 0;
[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to be even")]
-public static bool IsEven(this int value) => value % 2 == 0;
+public static bool HasEvenValue(this int value) => value % 2 == 0;
// Usage:
await Assert.That(10)
.IsPositive()
- .And.IsEven();
+ .And.HasEvenValue();
// Or:
+var number = 2;
await Assert.That(number)
- .IsEven()
+ .HasEvenValue()
.Or.IsPositive();
```
@@ -486,7 +490,7 @@ public static partial class UserAssertionExtensions
[GenerateAssertion(ExpectationMessage = "to have valid ID")]
public static bool HasValidId(this User user)
{
- return user.Id > 0;
+ return user.Id is int id && id > 0;
}
// With parameters
@@ -514,9 +518,9 @@ public static partial class UserAssertionExtensions
// Async with database
[EditorBrowsable(EditorBrowsableState.Never)]
[GenerateAssertion(ExpectationMessage = "to exist in database")]
- public static async Task ExistsInDatabaseAsync(this User user, DbContext db)
+ public static async Task ExistsInDatabaseAsync(this User user, ApplicationDbContext db)
{
- return await db.Users.AnyAsync(u => u.Id == user.Id);
+ return await db.Users.AnyAsync(u => Equals(u.Id, user.Id));
}
}
@@ -525,6 +529,7 @@ public static partial class UserAssertionExtensions
public async Task ValidateUser()
{
var user = new User { Id = 1, Email = "test@example.com", Roles = ["Admin"] };
+ await using var dbContext = new ApplicationDbContext();
await Assert.That(user).HasValidId();
await Assert.That(user).HasRole("Admin");
diff --git a/docs/docs/assertions/getting-started.md b/docs/docs/assertions/getting-started.md
index 05707d8fa94..ade135f010a 100644
--- a/docs/docs/assertions/getting-started.md
+++ b/docs/docs/assertions/getting-started.md
@@ -2,7 +2,6 @@
sidebar_position: 1
---
-
# Getting Started with Assertions
@@ -12,7 +11,6 @@ TUnit provides a comprehensive, fluent assertion library that makes your tests r
All assertions in TUnit follow a consistent pattern using the `Assert.That()` method:
-
```csharp
await Assert.That(actualValue).IsEqualTo(expectedValue);
```
@@ -26,8 +24,7 @@ The basic flow is:
TUnit assertions must be awaited — they won't execute without `await`, and the test will pass silently:
-
-```csharp
+```text
// ✅ Correct - awaited
await Assert.That(result).IsEqualTo(42);
@@ -45,7 +42,6 @@ TUnit provides assertions for all common scenarios:
### Equality & Comparison
-
```csharp
await Assert.That(actual).IsEqualTo(expected);
await Assert.That(value).IsNotEqualTo(other);
@@ -56,7 +52,6 @@ await Assert.That(temperature).IsBetween(20, 30);
### Strings
-
```csharp
await Assert.That(message).Contains("Hello");
await Assert.That(filename).StartsWith("test_");
@@ -66,7 +61,6 @@ await Assert.That(input).IsNotEmpty();
### Collections
-
```csharp
await Assert.That(numbers).Contains(42);
await Assert.That(items).Count().IsEqualTo(5);
@@ -76,16 +70,14 @@ await Assert.That(values).All(x => x > 0);
### Booleans & Null
-
```csharp
await Assert.That(isValid).IsTrue();
-await Assert.That(result).IsNotNull();
+await Assert.That(obj).IsNotNull();
await Assert.That(optional).IsDefault();
```
### Exceptions
-
```csharp
await Assert.That(() => DivideByZero())
.Throws()
@@ -94,7 +86,6 @@ await Assert.That(() => DivideByZero())
### Type Checking
-
```csharp
await Assert.That(obj).IsTypeOf();
await Assert.That(typeof(Dog)).IsAssignableTo();
@@ -104,18 +95,14 @@ await Assert.That(typeof(Dog)).IsAssignableTo();
Combine multiple assertions on the same value using `.And`:
-
```csharp
-await Assert.That(username)
- .IsNotNull()
- .And.IsNotEmpty()
- .And.Length().IsGreaterThan(3)
- .And.Length().IsLessThan(20);
+await Assert.That(username).IsNotNull().And.IsNotEmpty();
+await Assert.That(username).Length().IsGreaterThan(3);
+await Assert.That(username).Length().IsLessThan(20);
```
Use `.Or` when any condition can be true:
-
```csharp
await Assert.That(statusCode)
.IsEqualTo(200)
@@ -127,7 +114,6 @@ await Assert.That(statusCode)
Group related assertions together so all failures are reported:
-
```csharp
using (Assert.Multiple())
{
@@ -144,7 +130,6 @@ Instead of stopping at the first failure, `Assert.Multiple()` runs all assertion
Assert on object properties using `.Member()`:
-
```csharp
await Assert.That(person)
.Member(p => p.Name, name => name.IsEqualTo("Alice"))
@@ -153,7 +138,6 @@ await Assert.That(person)
This works with nested properties too:
-
```csharp
await Assert.That(order)
.Member(o => o.Customer.Address.City, city => city.IsEqualTo("Seattle"));
@@ -163,7 +147,6 @@ await Assert.That(order)
Collections have rich assertion support:
-
```csharp
var numbers = new[] { 1, 2, 3, 4, 5 };
@@ -190,7 +173,6 @@ await Assert.That(numbers).IsEquivalentTo(new[] { 5, 4, 3, 2, 1 });
Some assertions return the value being tested, allowing you to continue working with it:
-
```csharp
// HasSingleItem returns the single item
var user = await Assert.That(users).HasSingleItem();
@@ -205,24 +187,21 @@ await Assert.That(admin.Permissions).IsNotEmpty();
Use `.Satisfies()` for custom conditions:
-
```csharp
await Assert.That(value).Satisfies(v => v % 2 == 0, "Value must be even");
```
Or map to a different value before asserting:
-
```csharp
await Assert.That(order)
- .Satisfies(o => o.Total, total => total > 100);
+ .Member(o => o.Total, total => total.IsGreaterThan(100));
```
## Common Patterns
### Testing Numeric Ranges
-
```csharp
await Assert.That(score).IsBetween(0, 100);
await Assert.That(temperature).IsGreaterThanOrEqualTo(32);
@@ -232,14 +211,12 @@ await Assert.That(temperature).IsGreaterThanOrEqualTo(32);
For floating-point comparisons:
-
```csharp
await Assert.That(3.14159).IsEqualTo(Math.PI).Within(0.001);
```
### Testing Async Operations
-
```csharp
await Assert.That(async () => await FetchDataAsync())
.Throws();
@@ -249,11 +226,10 @@ await Assert.That(longRunningTask).CompletesWithin(TimeSpan.FromSeconds(5));
### Testing Multiple Conditions
-
```csharp
await Assert.That(username)
.IsNotNull()
- .And.Satisfies(name => name.Length >= 3 && name.Length <= 20,
+ .And.Satisfies(name => name!.Length >= 3 && name.Length <= 20,
"Username must be 3-20 characters");
```
@@ -261,10 +237,10 @@ await Assert.That(username)
TUnit's assertions are strongly typed and catch type mismatches at compile time:
-
```csharp
int number = 42;
string text = "42";
+_ = text;
// ✅ This works - both are ints
await Assert.That(number).IsEqualTo(42);
diff --git a/docs/docs/assertions/member-assertions.md b/docs/docs/assertions/member-assertions.md
index 507ec324a99..9223522e7b2 100644
--- a/docs/docs/assertions/member-assertions.md
+++ b/docs/docs/assertions/member-assertions.md
@@ -2,7 +2,6 @@
sidebar_position: 12
---
-
# Member Assertions
@@ -10,7 +9,6 @@ The `.Member()` method allows you to assert on object properties while maintaini
## Basic Usage
-
```csharp
[Test]
public async Task BasicMemberAssertions()
@@ -33,7 +31,6 @@ public async Task BasicMemberAssertions()
The key advantage of `.Member()` is that it returns to the parent context after each assertion, allowing you to chain multiple property checks:
-
```csharp
[Test]
public async Task MemberAssertionsWithFullContext()
@@ -53,7 +50,6 @@ public async Task MemberAssertionsWithFullContext()
Member assertions support nested properties:
-
```csharp
[Test]
public async Task NestedPropertyAssertions()
@@ -72,7 +68,6 @@ public async Task NestedPropertyAssertions()
You can perform complex assertions on member values, including collections:
-
```csharp
[Test]
public async Task ComplexMemberAssertions()
@@ -94,7 +89,6 @@ public async Task ComplexMemberAssertions()
Member assertions work with both `.And` and `.Or` combinators:
-
```csharp
[Test]
public async Task MemberAssertionsWithOrLogic()
@@ -116,7 +110,6 @@ public async Task MemberAssertionsWithOrLogic()
## Complete Example
-
```csharp
[Test]
public async Task ComplexObjectValidation()
@@ -134,7 +127,6 @@ public async Task ComplexObjectValidation()
## Nested Object Assertions
-
```csharp
[Test]
public async Task NestedObjectAssertions()
@@ -145,9 +137,9 @@ public async Task NestedObjectAssertions()
.IsNotNull()
.And.Member(c => c.Name, name => name.IsEqualTo("TechCorp"))
.And.Member(c => c.Address.City, city => city.IsEqualTo("Seattle"))
- .And.Member(c => c.Address.ZipCode, zip => zip.Matches(@"^\d{5}$"))
- .And.Member(c => c.Employees, employees => employees
- .Count().IsBetween(100, 500)
- .And.All(e => e.Email.EndsWith("@techcorp.com")));
+ .And.Member(c => c.Address.ZipCode, zip => zip.Matches(@"^\d{5}$"));
+
+ await Assert.That(company.Employees.Length).IsBetween(1, 500);
+ await Assert.That(company.Employees).All(e => e.Email.EndsWith("@example.com"));
}
```
diff --git a/docs/docs/assertions/null-and-default.md b/docs/docs/assertions/null-and-default.md
index 0d72deea596..6fca6380a99 100644
--- a/docs/docs/assertions/null-and-default.md
+++ b/docs/docs/assertions/null-and-default.md
@@ -2,7 +2,6 @@
sidebar_position: 2.5
---
-
# Null and Default Value Assertions
@@ -14,12 +13,11 @@ TUnit provides assertions for testing null values and default values. These asse
Tests that a value is `null`:
-
```csharp
[Test]
public async Task Null_Value()
{
- string? result = GetOptionalValue();
+ string? result = GetOptionalString();
await Assert.That(result).IsNull();
Person? person = FindPerson("unknown-id");
@@ -31,7 +29,6 @@ public async Task Null_Value()
Tests that a value is not `null`:
-
```csharp
[Test]
public async Task Not_Null_Value()
@@ -48,7 +45,6 @@ public async Task Not_Null_Value()
When you use `IsNotNull()`, C#'s nullability analysis understands that the value is non-null afterward:
-
```csharp
[Test]
public async Task Nullability_Flow()
@@ -65,7 +61,6 @@ public async Task Nullability_Flow()
This works with chaining too:
-
```csharp
[Test]
public async Task Chained_After_Null_Check()
@@ -85,7 +80,6 @@ public async Task Chained_After_Null_Check()
Tests that a value equals the default value for its type:
-
```csharp
[Test]
public async Task Default_Values()
@@ -113,7 +107,6 @@ public async Task Default_Values()
Tests that a value is not the default value for its type:
-
```csharp
[Test]
public async Task Not_Default_Values()
@@ -138,7 +131,6 @@ public async Task Not_Default_Values()
For reference types, default equals `null`:
-
```csharp
[Test]
public async Task Reference_Type_Defaults()
@@ -157,7 +149,6 @@ public async Task Reference_Type_Defaults()
For value types, default is the zero-initialized value:
-
```csharp
[Test]
public async Task Value_Type_Defaults()
@@ -187,7 +178,6 @@ public async Task Value_Type_Defaults()
Nullable value types (`T?`) are reference types, so their default is `null`:
-
```csharp
[Test]
public async Task Nullable_Value_Type_Defaults()
@@ -206,7 +196,6 @@ public async Task Nullable_Value_Type_Defaults()
### Optional Parameters and Returns
-
```csharp
[Test]
public async Task Optional_Return_Value()
@@ -223,7 +212,6 @@ public async Task Optional_Return_Value()
### Initialization Checks
-
```csharp
[Test]
public async Task Uninitialized_Field()
@@ -242,7 +230,6 @@ public async Task Uninitialized_Field()
### Dependency Injection Validation
-
```csharp
[Test]
public async Task Constructor_Injection()
@@ -257,7 +244,6 @@ public async Task Constructor_Injection()
### Lazy Initialization
-
```csharp
[Test]
public async Task Lazy_Property()
@@ -278,7 +264,6 @@ public async Task Lazy_Property()
Use `Assert.Multiple()` to check multiple null conditions:
-
```csharp
[Test]
public async Task Validate_All_Required_Fields()
@@ -298,7 +283,6 @@ public async Task Validate_All_Required_Fields()
Or chain them:
-
```csharp
[Test]
public async Task Required_Fields_With_Chaining()
@@ -307,8 +291,8 @@ public async Task Required_Fields_With_Chaining()
await Assert.That(config.DatabaseConnection)
.IsNotNull()
- .And.Member(c => c.Server).IsNotNull()
- .And.Member(c => c.Database).IsNotNull();
+ .And.Member(c => c.Server, server => server.IsNotNull())
+ .And.Member(c => c.Database, database => database.IsNotNull());
}
```
@@ -316,7 +300,6 @@ public async Task Required_Fields_With_Chaining()
### Structs
-
```csharp
public struct Rectangle
{
@@ -337,7 +320,6 @@ public async Task Struct_Default()
### Records
-
```csharp
public record Person(string Name, int Age);
@@ -365,7 +347,6 @@ public async Task Record_Struct_Default()
### Empty Collections vs Null
-
```csharp
[Test]
public async Task Empty_vs_Null()
@@ -381,7 +362,6 @@ public async Task Empty_vs_Null()
### Empty Strings vs Null
-
```csharp
[Test]
public async Task Empty_String_vs_Null()
@@ -397,7 +377,6 @@ public async Task Empty_String_vs_Null()
### Default GUID
-
```csharp
[Test]
public async Task GUID_Default()
@@ -412,7 +391,6 @@ public async Task GUID_Default()
### Default DateTime
-
```csharp
[Test]
public async Task DateTime_Default()
@@ -428,7 +406,6 @@ public async Task DateTime_Default()
### Validate Required Dependencies
-
```csharp
[Test]
public async Task All_Dependencies_Provided()
@@ -443,7 +420,6 @@ public async Task All_Dependencies_Provided()
### Validate Optional Features
-
```csharp
[Test]
public async Task Optional_Feature_Not_Enabled()
@@ -459,7 +435,6 @@ public async Task Optional_Feature_Not_Enabled()
### State Machine Validation
-
```csharp
[Test]
public async Task State_Transitions()
diff --git a/docs/docs/assertions/numeric.md b/docs/docs/assertions/numeric.md
index 42fbdc30c63..3860a10a422 100644
--- a/docs/docs/assertions/numeric.md
+++ b/docs/docs/assertions/numeric.md
@@ -2,7 +2,6 @@
sidebar_position: 4.5
---
-
# Numeric Assertions
@@ -14,7 +13,6 @@ TUnit provides comprehensive assertions for testing numeric values, including sp
Tests that a numeric value is greater than zero:
-
```csharp
[Test]
public async Task Positive_Values()
@@ -32,7 +30,6 @@ public async Task Positive_Values()
Works with all numeric types:
-
```csharp
[Test]
public async Task All_Numeric_Types()
@@ -54,7 +51,6 @@ public async Task All_Numeric_Types()
Tests that a numeric value is less than zero:
-
```csharp
[Test]
public async Task Negative_Values()
@@ -72,7 +68,6 @@ public async Task Negative_Values()
### Zero is Neither Positive Nor Negative
-
```csharp
[Test]
public async Task Zero_Checks()
@@ -94,7 +89,6 @@ All comparison operators work with numeric types. See [Equality and Comparison](
### Quick Reference
-
```csharp
[Test]
public async Task Numeric_Comparisons()
@@ -115,7 +109,6 @@ Floating-point arithmetic can introduce rounding errors. Use tolerance for safe
### Double Tolerance
-
```csharp
[Test]
public async Task Double_Tolerance()
@@ -133,7 +126,6 @@ public async Task Double_Tolerance()
### Float Tolerance
-
```csharp
[Test]
public async Task Float_Tolerance()
@@ -149,7 +141,6 @@ public async Task Float_Tolerance()
Useful for monetary calculations:
-
```csharp
[Test]
public async Task Decimal_Tolerance()
@@ -165,7 +156,6 @@ public async Task Decimal_Tolerance()
For timestamp or large number comparisons:
-
```csharp
[Test]
public async Task Long_Tolerance()
@@ -183,7 +173,6 @@ public async Task Long_Tolerance()
### Financial Calculations
-
```csharp
[Test]
public async Task Calculate_Total_Price()
@@ -203,7 +192,6 @@ public async Task Calculate_Total_Price()
### Temperature Conversions
-
```csharp
[Test]
public async Task Celsius_To_Fahrenheit()
@@ -218,7 +206,6 @@ public async Task Celsius_To_Fahrenheit()
### Percentage Calculations
-
```csharp
[Test]
public async Task Calculate_Percentage()
@@ -235,7 +222,6 @@ public async Task Calculate_Percentage()
### Statistical Calculations
-
```csharp
[Test]
public async Task Calculate_Average()
@@ -253,7 +239,6 @@ public async Task Calculate_Average()
### Valid Range Checks
-
```csharp
[Test]
public async Task Validate_Age()
@@ -267,7 +252,6 @@ public async Task Validate_Age()
### Percentage Range
-
```csharp
[Test]
public async Task Validate_Percentage()
@@ -281,7 +265,6 @@ public async Task Validate_Percentage()
### Score Validation
-
```csharp
[Test]
public async Task Validate_Score()
@@ -297,7 +280,6 @@ public async Task Validate_Score()
## Chaining Numeric Assertions
-
```csharp
[Test]
public async Task Chained_Numeric_Assertions()
@@ -314,7 +296,6 @@ public async Task Chained_Numeric_Assertions()
## Nullable Numeric Types
-
```csharp
[Test]
public async Task Nullable_Numerics()
@@ -339,7 +320,6 @@ public async Task Nullable_Null()
### Infinity
-
```csharp
[Test]
public async Task Infinity_Checks()
@@ -354,7 +334,6 @@ public async Task Infinity_Checks()
### NaN (Not a Number)
-
```csharp
[Test]
public async Task NaN_Checks()
@@ -371,7 +350,6 @@ public async Task NaN_Checks()
## Performance Metrics
-
```csharp
[Test]
public async Task Response_Time_Check()
@@ -391,7 +369,6 @@ public async Task Response_Time_Check()
### Boundary Testing
-
```csharp
[Test]
public async Task Boundary_Values()
@@ -407,7 +384,6 @@ public async Task Boundary_Values()
### Growth Rate Validation
-
```csharp
[Test]
public async Task Growth_Rate()
@@ -423,7 +399,6 @@ public async Task Growth_Rate()
### Ratio Calculations
-
```csharp
[Test]
public async Task Success_Ratio()
diff --git a/docs/docs/assertions/regex-assertions.md b/docs/docs/assertions/regex-assertions.md
index b834d5c1ac2..b57bb453786 100644
--- a/docs/docs/assertions/regex-assertions.md
+++ b/docs/docs/assertions/regex-assertions.md
@@ -2,7 +2,6 @@
sidebar_position: 13
---
-
# Regex Assertions
@@ -40,7 +39,6 @@ The key advantage of regex assertions is the ability to assert on capture groups
### Named Groups
-
```csharp
[Test]
public async Task NamedGroupAssertions()
@@ -58,7 +56,6 @@ public async Task NamedGroupAssertions()
### Indexed Groups
-
```csharp
[Test]
public async Task IndexedGroupAssertions()
@@ -80,7 +77,6 @@ public async Task IndexedGroupAssertions()
When a regex matches multiple times in a string, you can access specific matches using `.Match(index)`:
-
```csharp
[Test]
public async Task MultipleMatchAssertions()
@@ -105,7 +101,6 @@ public async Task MultipleMatchAssertions()
To assert on where a match occurs or how long it is, use `.Match(index)` to select a match from the collection, then assert on the resulting `RegexMatch` (you can also combine this with `Regex.Match(...)` directly if you need more detailed inspection):
-
```csharp
[Test]
public async Task PositionAndLengthAssertions()
@@ -128,7 +123,6 @@ public async Task PositionAndLengthAssertions()
## Complex Patterns with Multiple Groups
-
```csharp
[Test]
public async Task ComplexPatternAssertions()
@@ -147,7 +141,6 @@ public async Task ComplexPatternAssertions()
## Product Information Validation
-
```csharp
[Test]
public async Task ProductCodeValidation()
@@ -159,13 +152,12 @@ public async Task ProductCodeValidation()
.Matches(pattern)
.And.Group("code", code => code.StartsWith("ABC"))
.And.Group("price", price => price.Contains(".99"))
- .And.Group("stock", stock => stock.Length().IsEqualTo(2));
+ .And.Group("stock", stock => stock.Satisfies(value => Regex.IsMatch(value!, @"^\d{2}$")));
}
```
## URL Parsing
-
```csharp
[Test]
public async Task UrlParsingAssertions()
@@ -187,7 +179,6 @@ public async Task UrlParsingAssertions()
The `Matches(string)` overload does not take `RegexOptions`. To apply options like case-insensitivity, construct a `Regex` (or use a source-generated regex) with the desired options and pass it to `Matches`:
-
```csharp
[Test]
public async Task RegexOptionsAssertions()
@@ -233,7 +224,6 @@ public partial class MyTests
Handle optional capture groups that may be empty:
-
```csharp
[Test]
public async Task OptionalGroupAssertions()
@@ -258,7 +248,6 @@ public async Task OptionalGroupAssertions()
## Complete Example
-
```csharp
[Test]
public async Task CompleteEmailValidation()
@@ -271,7 +260,7 @@ public async Task CompleteEmailValidation()
.And.Group("local", local => local.StartsWith("john"))
.And.Group("subdomain", sub => sub.IsEqualTo("mail"))
.And.Group("domain", domain => domain.IsEqualTo("example"))
- .And.Group("tld", tld => tld.Length().IsEqualTo(3));
+ .And.Group("tld", tld => tld.Satisfies(value => Regex.IsMatch(value!, @"^\w{3}$")));
// For position/length checks, use Regex.Match directly
var match = System.Text.RegularExpressions.Regex.Match(email, pattern);
@@ -284,7 +273,6 @@ public async Task CompleteEmailValidation()
The regex assertions surface standard exceptions for common error cases. Wrap the call in an `Assert.That(() => ...)` delegate and assert on the thrown exception type:
-
```csharp
[Test]
public async Task RegexAssertionErrors()
diff --git a/docs/docs/assertions/should-syntax.md b/docs/docs/assertions/should-syntax.md
index 974ced258f0..c4e01ff1865 100644
--- a/docs/docs/assertions/should-syntax.md
+++ b/docs/docs/assertions/should-syntax.md
@@ -4,7 +4,6 @@ title: Should Syntax (Optional)
description: FluentAssertions-style value.Should().BeEqualTo() syntax via the optional TUnit.Assertions.Should NuGet package.
---
-
# Should Syntax
@@ -56,24 +55,22 @@ The `Does*` strip rule reads naturally for verbs (`DoesMatch` → `Match`, `Does
For irregulars or when the conjugation produces an unwanted name, decorate the assertion class with `[ShouldName("...")]`. The override is consulted before the conjugation rules:
-
```csharp
[AssertionExtension("IsOdd")]
[ShouldName("BeAnOddNumber")]
-public class OddAssertion : Assertion { … }
+public abstract class OddAssertion(AssertionContext context) : Assertion(context) { }
```
`[AssertionExtension(NegatedMethodName = "...")]` produces a second extension method for the negated form, which the Should generator picks up and conjugates independently — `Contains` → `Contain` and `DoesNotContain` → `NotContain` come out automatically without any `[ShouldName]`. When TUnit's pattern uses **separate classes** for positive and negated forms (e.g. `EqualsAssertion` + `NotEqualsAssertion`), place a separate `[ShouldName]` on each:
-
```csharp
[AssertionExtension("IsBetween")]
[ShouldName("BeWithinRange")]
-public class BetweenAssertion : Assertion { … }
+public abstract class BetweenAssertion(AssertionContext context) : Assertion(context) { }
[AssertionExtension("IsNotBetween")]
[ShouldName("NotBeWithinRange")]
-public class NotBetweenAssertion : Assertion { … }
+public abstract class NotBetweenAssertion(AssertionContext context) : Assertion(context) { }
```
## Entry Points
@@ -84,7 +81,7 @@ Each entry overload returns a wrapper appropriate to the source type:
// Value entry — returns ShouldSource
await 42.Should().BeEqualTo(42);
await "hello".Should().Contain("ell");
-await someObject.Should().BeOfType();
+await someObject.Should().BeOfType(typeof(MyClass));
// Collection entry — returns ShouldCollectionSource
// exposes element-typed instance methods (BeInOrder, All, Any,
@@ -113,7 +110,7 @@ await value
.And.NotBeEqualTo(7)
.And.BeBetween(1, 10);
-await statusCode
+await ((int) statusCode)
.Should().BeEqualTo(200)
.Or.BeEqualTo(201)
.Or.BeEqualTo(204);
diff --git a/docs/docs/assertions/specialized-types.md b/docs/docs/assertions/specialized-types.md
index 097eb7353b4..3593b97f2a8 100644
--- a/docs/docs/assertions/specialized-types.md
+++ b/docs/docs/assertions/specialized-types.md
@@ -2,7 +2,6 @@
sidebar_position: 12
---
-
# Specialized Type Assertions
@@ -14,7 +13,6 @@ TUnit provides assertions for many specialized .NET types beyond the common prim
Tests whether a GUID is empty (`Guid.Empty`):
-
```csharp
[Test]
public async Task GUID_Is_Empty()
@@ -29,7 +27,6 @@ public async Task GUID_Is_Empty()
Practical usage:
-
```csharp
[Test]
public async Task Entity_Has_Valid_ID()
@@ -47,7 +44,6 @@ public async Task Entity_Has_Valid_ID()
Tests for 2xx success status codes:
-
```csharp
[Test]
public async Task HTTP_Success_Status()
@@ -60,7 +56,6 @@ public async Task HTTP_Success_Status()
Works with all 2xx codes:
-
```csharp
[Test]
public async Task Various_Success_Codes()
@@ -74,7 +69,6 @@ public async Task Various_Success_Codes()
### IsNotSuccess
-
```csharp
[Test]
public async Task HTTP_Not_Success()
@@ -88,7 +82,6 @@ public async Task HTTP_Not_Success()
Tests for 4xx client error status codes:
-
```csharp
[Test]
public async Task HTTP_Client_Error()
@@ -104,7 +97,6 @@ public async Task HTTP_Client_Error()
Tests for 5xx server error status codes:
-
```csharp
[Test]
public async Task HTTP_Server_Error()
@@ -119,7 +111,6 @@ public async Task HTTP_Server_Error()
Tests for 3xx redirection status codes:
-
```csharp
[Test]
public async Task HTTP_Redirection()
@@ -134,7 +125,6 @@ public async Task HTTP_Redirection()
### IsCancellationRequested / IsNotCancellationRequested
-
```csharp
[Test]
public async Task CancellationToken_Is_Requested()
@@ -156,7 +146,6 @@ public async Task CancellationToken_Not_Requested()
### CanBeCanceled / CannotBeCanceled
-
```csharp
[Test]
public async Task Token_Can_Be_Canceled()
@@ -179,7 +168,6 @@ public async Task Default_Token_Cannot_Be_Canceled()
### IsLetter / IsNotLetter
-
```csharp
[Test]
public async Task Char_Is_Letter()
@@ -194,7 +182,6 @@ public async Task Char_Is_Letter()
### IsDigit / IsNotDigit
-
```csharp
[Test]
public async Task Char_Is_Digit()
@@ -208,7 +195,6 @@ public async Task Char_Is_Digit()
### IsWhiteSpace / IsNotWhiteSpace
-
```csharp
[Test]
public async Task Char_Is_WhiteSpace()
@@ -223,7 +209,6 @@ public async Task Char_Is_WhiteSpace()
### IsUpper / IsNotUpper
-
```csharp
[Test]
public async Task Char_Is_Upper()
@@ -237,7 +222,6 @@ public async Task Char_Is_Upper()
### IsLower / IsNotLower
-
```csharp
[Test]
public async Task Char_Is_Lower()
@@ -251,7 +235,6 @@ public async Task Char_Is_Lower()
### IsPunctuation / IsNotPunctuation
-
```csharp
[Test]
public async Task Char_Is_Punctuation()
@@ -270,7 +253,6 @@ public async Task Char_Is_Punctuation()
#### Exists / DoesNotExist
-
```csharp
[Test]
public async Task Directory_Exists()
@@ -291,7 +273,6 @@ public async Task Directory_Does_Not_Exist()
#### HasFiles / IsEmpty
-
```csharp
[Test]
public async Task Directory_Has_Files()
@@ -316,14 +297,13 @@ public async Task Directory_Is_Empty()
#### HasSubdirectories / HasNoSubdirectories
-
```csharp
[Test]
public async Task Directory_Has_Subdirectories()
{
var windowsDir = new DirectoryInfo(@"C:\Windows");
- await Assert.That(windowsDir).HasSubdirectories();
+ await Assert.That(windowsDir.EnumerateDirectories().Any()).IsTrue();
}
```
@@ -331,7 +311,6 @@ public async Task Directory_Has_Subdirectories()
#### Exists / DoesNotExist
-
```csharp
[Test]
public async Task File_Exists()
@@ -356,7 +335,6 @@ public async Task File_Does_Not_Exist()
#### IsReadOnly / IsNotReadOnly
-
```csharp
[Test]
public async Task File_Is_ReadOnly()
@@ -377,7 +355,6 @@ public async Task File_Is_ReadOnly()
#### IsHidden / IsNotHidden
-
```csharp
[Test]
public async Task File_Is_Hidden()
@@ -396,7 +373,6 @@ public async Task File_Is_Hidden()
#### IsSystem / IsNotSystem
-
```csharp
[Test]
public async Task File_Is_System()
@@ -406,14 +382,13 @@ public async Task File_Is_System()
if (systemFile.Exists)
{
- await Assert.That(systemFile).IsSystem();
+ await Assert.That(systemFile.Attributes.HasFlag(FileAttributes.System)).IsTrue();
}
}
```
#### IsExecutable / IsNotExecutable
-
```csharp
[Test]
public async Task File_Is_Executable()
@@ -422,7 +397,7 @@ public async Task File_Is_Executable()
if (exeFile.Exists)
{
- await Assert.That(exeFile).IsExecutable();
+ await Assert.That(exeFile.Extension).IsEqualTo(".exe");
}
}
```
@@ -431,14 +406,13 @@ public async Task File_Is_Executable()
### IsIPv4 / IsNotIPv4
-
```csharp
[Test]
public async Task IP_Is_IPv4()
{
var ipv4 = IPAddress.Parse("192.168.1.1");
- await Assert.That(ipv4).IsIPv4();
+ await Assert.That(ipv4.AddressFamily).IsEqualTo(AddressFamily.InterNetwork);
}
[Test]
@@ -446,20 +420,19 @@ public async Task IP_Not_IPv4()
{
var ipv6 = IPAddress.Parse("::1");
- await Assert.That(ipv6).IsNotIPv4();
+ await Assert.That(ipv6.AddressFamily).IsNotEqualTo(AddressFamily.InterNetwork);
}
```
### IsIPv6 / IsNotIPv6
-
```csharp
[Test]
public async Task IP_Is_IPv6()
{
var ipv6 = IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
- await Assert.That(ipv6).IsIPv6();
+ await Assert.That(ipv6.AddressFamily).IsEqualTo(AddressFamily.InterNetworkV6);
}
[Test]
@@ -467,7 +440,7 @@ public async Task IP_Not_IPv6()
{
var ipv4 = IPAddress.Parse("127.0.0.1");
- await Assert.That(ipv4).IsNotIPv6();
+ await Assert.That(ipv4.AddressFamily).IsNotEqualTo(AddressFamily.InterNetworkV6);
}
```
@@ -475,14 +448,13 @@ public async Task IP_Not_IPv6()
### IsValueCreated / IsNotValueCreated
-
```csharp
[Test]
public async Task Lazy_Value_Not_Created()
{
var lazy = new Lazy(() => 42);
- await Assert.That(lazy).IsNotValueCreated();
+ await Assert.That(lazy.IsValueCreated).IsFalse();
var value = lazy.Value;
@@ -495,27 +467,25 @@ public async Task Lazy_Value_Not_Created()
### CanRead / CannotRead
-
```csharp
[Test]
public async Task Stream_Can_Read()
{
using var stream = new MemoryStream();
- await Assert.That(stream).CanRead();
+ await Assert.That((Stream) stream).CanRead();
}
```
### CanWrite / CannotWrite
-
```csharp
[Test]
public async Task Stream_Can_Write()
{
using var stream = new MemoryStream();
- await Assert.That(stream).CanWrite();
+ await Assert.That((Stream) stream).CanWrite();
}
[Test]
@@ -523,26 +493,24 @@ public async Task Stream_Cannot_Write()
{
var readOnlyStream = new MemoryStream(new byte[10], writable: false);
- await Assert.That(readOnlyStream).CannotWrite();
+ await Assert.That((Stream) readOnlyStream).CannotWrite();
}
```
### CanSeek / CannotSeek
-
```csharp
[Test]
public async Task Stream_Can_Seek()
{
using var stream = new MemoryStream();
- await Assert.That(stream).CanSeek();
+ await Assert.That((Stream) stream).CanSeek();
}
```
### CanTimeout / CannotTimeout
-
```csharp
[Test]
public async Task Network_Stream_Can_Timeout()
@@ -557,7 +525,6 @@ public async Task Network_Stream_Can_Timeout()
### HasExited / HasNotExited
-
```csharp
[Test]
public async Task Process_Has_Not_Exited()
@@ -575,14 +542,13 @@ public async Task Process_Has_Not_Exited()
### IsResponding / IsNotResponding
-
```csharp
[Test]
public async Task Process_Is_Responding()
{
var process = Process.GetCurrentProcess();
- await Assert.That(process).IsResponding();
+ await Assert.That(process.Responding).IsTrue();
}
```
@@ -590,7 +556,6 @@ public async Task Process_Is_Responding()
### IsAlive / IsNotAlive
-
```csharp
[Test]
public async Task Thread_Is_Alive()
@@ -607,7 +572,6 @@ public async Task Thread_Is_Alive()
### IsBackground / IsNotBackground
-
```csharp
[Test]
public async Task Thread_Is_Background()
@@ -621,7 +585,6 @@ public async Task Thread_Is_Background()
### IsThreadPoolThread / IsNotThreadPoolThread
-
```csharp
[Test]
public async Task Check_ThreadPool_Thread()
@@ -637,7 +600,6 @@ public async Task Check_ThreadPool_Thread()
### IsAlive / IsNotAlive
-
```csharp
[Test]
public async Task WeakReference_Is_Alive()
@@ -659,7 +621,6 @@ public async Task WeakReference_Is_Alive()
### IsAbsoluteUri / IsNotAbsoluteUri
-
```csharp
[Test]
public async Task URI_Is_Absolute()
@@ -682,14 +643,13 @@ public async Task URI_Is_Relative()
### IsUtf8 / IsNotUtf8
-
```csharp
[Test]
public async Task Encoding_Is_UTF8()
{
var encoding = Encoding.UTF8;
- await Assert.That(encoding).IsUtf8();
+ await Assert.That(encoding.WebName).IsEqualTo(Encoding.UTF8.WebName);
}
[Test]
@@ -697,7 +657,7 @@ public async Task Encoding_Not_UTF8()
{
var encoding = Encoding.ASCII;
- await Assert.That(encoding).IsNotUtf8();
+ await Assert.That(encoding.WebName).IsNotEqualTo(Encoding.UTF8.WebName);
}
```
@@ -705,7 +665,6 @@ public async Task Encoding_Not_UTF8()
Version comparisons using standard comparison operators:
-
```csharp
[Test]
public async Task Version_Comparison()
@@ -722,7 +681,6 @@ public async Task Version_Comparison()
### IsWeekend / IsNotWeekend
-
```csharp
[Test]
public async Task Day_Is_Weekend()
@@ -734,7 +692,6 @@ public async Task Day_Is_Weekend()
### IsWeekday / IsNotWeekday
-
```csharp
[Test]
public async Task Day_Is_Weekday()
@@ -751,7 +708,6 @@ public async Task Day_Is_Weekday()
### API Testing
-
```csharp
[Test]
public async Task API_Returns_Success()
@@ -765,7 +721,6 @@ public async Task API_Returns_Success()
### File Upload Validation
-
```csharp
[Test]
public async Task Uploaded_File_Validation()
@@ -780,7 +735,6 @@ public async Task Uploaded_File_Validation()
### Configuration Directory Check
-
```csharp
[Test]
public async Task Config_Directory_Setup()
@@ -794,14 +748,13 @@ public async Task Config_Directory_Setup()
### Network Validation
-
```csharp
[Test]
public async Task Server_IP_Is_Valid()
{
- var serverIp = IPAddress.Parse(Configuration["ServerIP"]);
+ var serverIp = IPAddress.Parse(Configuration["ServerIP"] ?? "127.0.0.1");
- await Assert.That(serverIp).IsIPv4();
+ await Assert.That(serverIp.AddressFamily).IsEqualTo(AddressFamily.InterNetwork);
}
```
diff --git a/docs/docs/assertions/string.md b/docs/docs/assertions/string.md
index e67c4263056..5f5412888fc 100644
--- a/docs/docs/assertions/string.md
+++ b/docs/docs/assertions/string.md
@@ -2,7 +2,6 @@
sidebar_position: 5.5
---
-
# String Assertions
@@ -14,7 +13,6 @@ TUnit provides rich assertions for testing strings, including substring matching
Tests that a string contains a substring:
-
```csharp
[Test]
public async Task String_Contains()
@@ -29,7 +27,6 @@ public async Task String_Contains()
#### Case-Insensitive Contains
-
```csharp
[Test]
public async Task Contains_Ignoring_Case()
@@ -48,7 +45,6 @@ public async Task Contains_Ignoring_Case()
#### With String Comparison
-
```csharp
[Test]
public async Task Contains_With_Comparison()
@@ -63,7 +59,6 @@ public async Task Contains_With_Comparison()
#### With Trimming
-
```csharp
[Test]
public async Task Contains_With_Trimming()
@@ -78,7 +73,6 @@ public async Task Contains_With_Trimming()
#### Ignoring Whitespace
-
```csharp
[Test]
public async Task Contains_Ignoring_Whitespace()
@@ -95,7 +89,6 @@ public async Task Contains_Ignoring_Whitespace()
Tests that a string does not contain a substring:
-
```csharp
[Test]
public async Task String_Does_Not_Contain()
@@ -109,7 +102,6 @@ public async Task String_Does_Not_Contain()
All modifiers work with `DoesNotContain`:
-
```csharp
[Test]
public async Task Does_Not_Contain_Ignoring_Case()
@@ -126,7 +118,6 @@ public async Task Does_Not_Contain_Ignoring_Case()
Tests that a string starts with a specific prefix:
-
```csharp
[Test]
public async Task String_Starts_With()
@@ -142,7 +133,6 @@ public async Task String_Starts_With()
With case-insensitive comparison:
-
```csharp
[Test]
public async Task Starts_With_Ignoring_Case()
@@ -159,7 +149,6 @@ public async Task Starts_With_Ignoring_Case()
Tests that a string ends with a specific suffix:
-
```csharp
[Test]
public async Task String_Ends_With()
@@ -175,7 +164,6 @@ public async Task String_Ends_With()
With case-insensitive comparison:
-
```csharp
[Test]
public async Task Ends_With_Ignoring_Case()
@@ -194,7 +182,6 @@ public async Task Ends_With_Ignoring_Case()
Tests that a string matches a regular expression pattern:
-
```csharp
[Test]
public async Task String_Matches_Pattern()
@@ -207,7 +194,6 @@ public async Task String_Matches_Pattern()
With a compiled `Regex`:
-
```csharp
[Test]
public async Task Matches_With_Regex()
@@ -221,7 +207,6 @@ public async Task Matches_With_Regex()
#### Case-Insensitive Matching
-
```csharp
[Test]
public async Task Matches_Ignoring_Case()
@@ -236,7 +221,6 @@ public async Task Matches_Ignoring_Case()
#### With Regex Options
-
```csharp
[Test]
public async Task Matches_With_Options()
@@ -253,7 +237,6 @@ public async Task Matches_With_Options()
Tests that a string does not match a pattern:
-
```csharp
[Test]
public async Task String_Does_Not_Match()
@@ -270,7 +253,6 @@ public async Task String_Does_Not_Match()
Tests that a string is empty (`""`):
-
```csharp
[Test]
public async Task String_Is_Empty()
@@ -283,7 +265,6 @@ public async Task String_Is_Empty()
Note: This checks for an empty string, not `null`:
-
```csharp
[Test]
public async Task Empty_vs_Null()
@@ -301,7 +282,6 @@ public async Task Empty_vs_Null()
Tests that a string is not empty:
-
```csharp
[Test]
public async Task String_Is_Not_Empty()
@@ -316,7 +296,6 @@ public async Task String_Is_Not_Empty()
Tests that a string has a specific length:
-
```csharp
[Test]
public async Task String_Has_Length()
@@ -329,22 +308,19 @@ public async Task String_Has_Length()
With chained comparison:
-
```csharp
[Test]
public async Task Length_With_Comparison()
{
var username = "alice";
- await Assert.That(username)
- .Length().IsGreaterThan(3)
- .And.Length().IsLessThan(20);
+ await Assert.That(username).Length().IsGreaterThan(3);
+ await Assert.That(username).Length().IsLessThan(20);
}
```
Using `IsBetween`:
-
```csharp
[Test]
public async Task Length_Range()
@@ -361,7 +337,6 @@ public async Task Length_Range()
String equality with various comparison options:
-
```csharp
[Test]
public async Task String_Equality()
@@ -375,7 +350,6 @@ public async Task String_Equality()
#### Ignoring Case
-
```csharp
[Test]
public async Task Equality_Ignoring_Case()
@@ -391,7 +365,6 @@ public async Task Equality_Ignoring_Case()
#### With String Comparison
-
```csharp
[Test]
public async Task Equality_With_Comparison()
@@ -409,7 +382,6 @@ public async Task Equality_With_Comparison()
You can parse strings to other types and assert on the result:
-
```csharp
[Test]
public async Task Parse_String_To_Int()
@@ -421,7 +393,6 @@ public async Task Parse_String_To_Int()
}
```
-
```csharp
[Test]
public async Task Parse_String_To_DateTime()
@@ -437,23 +408,19 @@ public async Task Parse_String_To_DateTime()
### Email Validation
-
```csharp
[Test]
public async Task Validate_Email()
{
var email = "user@example.com";
- await Assert.That(email)
- .Contains("@")
- .And.Matches(@"^[\w\.-]+@[\w\.-]+\.\w+$")
- .And.DoesNotContain(" ");
+ await Assert.That(email).Contains("@").And.DoesNotContain(" ");
+ await Assert.That(email).Matches(@"^[\w\.-]+@[\w\.-]+\.\w+$");
}
```
### URL Validation
-
```csharp
[Test]
public async Task Validate_URL()
@@ -469,7 +436,6 @@ public async Task Validate_URL()
### File Extension Check
-
```csharp
[Test]
public async Task Check_File_Extension()
@@ -484,42 +450,37 @@ public async Task Check_File_Extension()
### Username Validation
-
```csharp
[Test]
public async Task Validate_Username()
{
var username = "alice_123";
- await Assert.That(username)
- .Length().IsGreaterThanOrEqualTo(3)
- .And.Length().IsLessThanOrEqualTo(20)
- .And.Matches(@"^[a-zA-Z0-9_]+$")
- .And.DoesNotContain(" ");
+ await Assert.That(username).Length().IsGreaterThanOrEqualTo(3);
+ await Assert.That(username).Length().IsLessThanOrEqualTo(20);
+ await Assert.That(username).Matches(@"^[a-zA-Z0-9_]+$");
+ await Assert.That(username).DoesNotContain(" ");
}
```
### Password Requirements
-
```csharp
[Test]
public async Task Validate_Password()
{
var password = "SecureP@ss123";
- await Assert.That(password)
- .Length().IsGreaterThanOrEqualTo(8)
- .And.Matches(@"[A-Z]") // Has uppercase
- .And.Matches(@"[a-z]") // Has lowercase
- .And.Matches(@"\d") // Has digit
- .And.Matches(@"[@$!%*?&]"); // Has special char
+ await Assert.That(password).Length().IsGreaterThanOrEqualTo(8);
+ await Assert.That(password).Matches(@"[A-Z]"); // Has uppercase
+ await Assert.That(password).Matches(@"[a-z]"); // Has lowercase
+ await Assert.That(password).Matches(@"\d"); // Has digit
+ await Assert.That(password).Matches(@"[@$!%*?&]"); // Has special char
}
```
### JSON String Content
-
```csharp
[Test]
public async Task Check_JSON_Content()
@@ -536,7 +497,6 @@ public async Task Check_JSON_Content()
### SQL Query Validation
-
```csharp
[Test]
public async Task Validate_SQL_Query()
@@ -554,7 +514,6 @@ public async Task Validate_SQL_Query()
### IsNullOrEmpty Equivalent
-
```csharp
[Test]
public async Task Check_Null_Or_Empty()
@@ -576,7 +535,6 @@ public async Task Check_Null_Or_Empty()
### IsNullOrWhiteSpace Equivalent
-
```csharp
[Test]
public async Task Check_Null_Or_Whitespace()
@@ -589,7 +547,6 @@ public async Task Check_Null_Or_Whitespace()
Better with trimming:
-
```csharp
[Test]
public async Task Require_Non_Whitespace()
@@ -609,7 +566,6 @@ public async Task Require_Non_Whitespace()
TUnit also supports assertions on `StringBuilder`:
-
```csharp
[Test]
public async Task StringBuilder_Tests()
@@ -628,7 +584,6 @@ public async Task StringBuilder_Tests()
## Chaining String Assertions
-
```csharp
[Test]
public async Task Chained_String_Assertions()
@@ -647,7 +602,6 @@ public async Task Chained_String_Assertions()
## Case Sensitivity Patterns
-
```csharp
[Test]
public async Task Case_Sensitivity()
@@ -671,7 +625,6 @@ public async Task Case_Sensitivity()
## String Formatting Validation
-
```csharp
[Test]
public async Task Formatted_String()
@@ -689,7 +642,6 @@ public async Task Formatted_String()
## Multi-line Strings
-
```csharp
[Test]
public async Task Multiline_String()
@@ -709,7 +661,6 @@ public async Task Multiline_String()
## Common String Comparison Options
-
```csharp
[Test]
public async Task String_Comparison_Options()
@@ -735,7 +686,6 @@ public async Task String_Comparison_Options()
## Path Validation
-
```csharp
[Test]
public async Task File_Path_Validation()
@@ -751,7 +701,6 @@ public async Task File_Path_Validation()
Unix path:
-
```csharp
[Test]
public async Task Unix_Path_Validation()
@@ -769,7 +718,6 @@ public async Task Unix_Path_Validation()
### Trim and Assert
-
```csharp
[Test]
public async Task Trim_Before_Assert()
@@ -783,7 +731,6 @@ public async Task Trim_Before_Assert()
### Case Normalization
-
```csharp
[Test]
public async Task Normalize_Case()
@@ -797,7 +744,6 @@ public async Task Normalize_Case()
### Substring Extraction
-
```csharp
[Test]
public async Task Extract_Substring()
diff --git a/docs/docs/assertions/tasks-and-async.md b/docs/docs/assertions/tasks-and-async.md
index 04d34be5cec..b5ac9b79fa8 100644
--- a/docs/docs/assertions/tasks-and-async.md
+++ b/docs/docs/assertions/tasks-and-async.md
@@ -2,7 +2,6 @@
sidebar_position: 11
---
-
# Task and Async Assertions
@@ -14,7 +13,6 @@ TUnit provides specialized assertions for testing `Task` and `Task` objects,
Tests whether a task has completed (successfully, faulted, or canceled):
-
```csharp
[Test]
public async Task Task_Is_Completed()
@@ -31,7 +29,6 @@ public async Task Task_Is_Completed()
Tests whether a task was canceled:
-
```csharp
[Test]
public async Task Task_Is_Canceled()
@@ -54,7 +51,6 @@ public async Task Task_Is_Canceled()
}
```
-
```csharp
[Test]
public async Task Task_Not_Canceled()
@@ -69,7 +65,6 @@ public async Task Task_Not_Canceled()
Tests whether a task ended in a faulted state (threw an exception):
-
```csharp
[Test]
public async Task Task_Is_Faulted()
@@ -89,7 +84,6 @@ public async Task Task_Is_Faulted()
}
```
-
```csharp
[Test]
public async Task Task_Not_Faulted()
@@ -104,7 +98,6 @@ public async Task Task_Not_Faulted()
Tests whether a task completed successfully (not faulted or canceled):
-
```csharp
[Test]
public async Task Task_Completed_Successfully()
@@ -115,7 +108,6 @@ public async Task Task_Completed_Successfully()
}
```
-
```csharp
[Test]
public async Task Task_Not_Completed_Successfully()
@@ -134,7 +126,6 @@ public async Task Task_Not_Completed_Successfully()
Tests that a task completes within a specified time:
-
```csharp
[Test]
public async Task Task_Completes_Within_Timeout()
@@ -147,7 +138,6 @@ public async Task Task_Completes_Within_Timeout()
Fails if timeout exceeded:
-
```csharp
[Test]
public async Task Task_Exceeds_Timeout()
@@ -163,7 +153,6 @@ public async Task Task_Exceeds_Timeout()
Polls a value source until a nested assertion passes or the timeout expires. `WaitsFor` takes an assertion-builder lambda (not a bool predicate), so you write the same fluent assertions you would elsewhere:
-
```csharp
[Test]
public async Task Wait_For_Condition()
@@ -187,14 +176,13 @@ public async Task Wait_For_Condition()
### API Call Timeout
-
```csharp
[Test]
public async Task API_Call_Completes_In_Time()
{
var apiTask = _httpClient.GetAsync("https://api.example.com/data");
- await Assert.That(apiTask).CompletesWithin(TimeSpan.FromSeconds(5));
+ await Assert.That((Func)(async () => { await apiTask; })).CompletesWithin(TimeSpan.FromSeconds(5));
var response = await apiTask;
await Assert.That(response.IsSuccessStatusCode).IsTrue();
@@ -203,7 +191,6 @@ public async Task API_Call_Completes_In_Time()
### Background Task Completion
-
```csharp
[Test]
public async Task Background_Processing_Completes()
@@ -217,7 +204,6 @@ public async Task Background_Processing_Completes()
### Cancellation Token Handling
-
```csharp
[Test]
public async Task Operation_Respects_Cancellation()
@@ -244,7 +230,6 @@ public async Task Operation_Respects_Cancellation()
For testing exceptions in async code, use exception assertions:
-
```csharp
[Test]
public async Task Async_Method_Throws_Exception()
@@ -258,7 +243,6 @@ public async Task Async_Method_Throws_Exception()
For `Task`, await the task first, then assert on the result:
-
```csharp
[Test]
public async Task Task_Returns_Expected_Result()
@@ -266,7 +250,7 @@ public async Task Task_Returns_Expected_Result()
var task = GetValueAsync();
// Ensure it completes in time
- await Assert.That(task).CompletesWithin(TimeSpan.FromSeconds(1));
+ await Assert.That((Func)(async () => { await task; })).CompletesWithin(TimeSpan.FromSeconds(1));
// Get the result
var result = await task;
@@ -278,7 +262,6 @@ public async Task Task_Returns_Expected_Result()
### Parallel Task Execution
-
```csharp
[Test]
public async Task Parallel_Tasks_Complete()
@@ -296,7 +279,6 @@ public async Task Parallel_Tasks_Complete()
### Task State Transitions
-
```csharp
[Test]
public async Task Task_State_Progression()
@@ -321,7 +303,6 @@ public async Task Task_State_Progression()
### Failed Task
-
```csharp
[Test]
public async Task Task_Fails_With_Exception()
@@ -338,7 +319,6 @@ public async Task Task_Fails_With_Exception()
### Canceled Task
-
```csharp
[Test]
public async Task Task_Can_Be_Canceled()
@@ -357,7 +337,6 @@ public async Task Task_Can_Be_Canceled()
### WhenAll Completion
-
```csharp
[Test]
public async Task All_Tasks_Complete()
@@ -374,7 +353,6 @@ public async Task All_Tasks_Complete()
### WhenAny Completion
-
```csharp
[Test]
public async Task Any_Task_Completes()
@@ -384,7 +362,7 @@ public async Task Any_Task_Completes()
var firstCompleted = Task.WhenAny(fastTask, slowTask);
- await Assert.That(firstCompleted).CompletesWithin(TimeSpan.FromMilliseconds(500));
+ await Assert.That((Func)(async () => { await firstCompleted; })).CompletesWithin(TimeSpan.FromMilliseconds(500));
var completed = await firstCompleted;
await Assert.That(completed).IsSameReferenceAs(fastTask);
@@ -395,7 +373,6 @@ public async Task Any_Task_Completes()
`ValueTask` and `ValueTask` work similarly:
-
```csharp
[Test]
public async Task ValueTask_Completion()
@@ -415,14 +392,13 @@ async ValueTask GetValueTaskAsync()
## Chaining Task Assertions
-
```csharp
[Test]
public async Task Chained_Task_Assertions()
{
var task = GetDataAsync();
- await Assert.That(task)
+ await Assert.That((Func)(async () => { await task; }))
.CompletesWithin(TimeSpan.FromSeconds(5));
await Assert.That(task)
@@ -437,7 +413,6 @@ public async Task Chained_Task_Assertions()
### Retry Logic Testing
-
```csharp
[Test]
public async Task Retry_Eventually_Succeeds()
@@ -452,7 +427,7 @@ public async Task Retry_Eventually_Succeeds()
return "Success";
}, maxRetries: 5);
- await Assert.That(task).CompletesWithin(TimeSpan.FromSeconds(10));
+ await Assert.That((Func)(async () => { await task; })).CompletesWithin(TimeSpan.FromSeconds(10));
var result = await task;
await Assert.That(result).IsEqualTo("Success");
}
@@ -460,7 +435,6 @@ public async Task Retry_Eventually_Succeeds()
### Debounce Testing
-
```csharp
[Test]
public async Task Debounced_Operation()
@@ -473,14 +447,13 @@ public async Task Debounced_Operation()
trigger.OnNext("value");
- await Assert.That(debouncedTask)
+ await Assert.That((Func)(async () => { await debouncedTask; }))
.CompletesWithin(TimeSpan.FromSeconds(1));
}
```
### Circuit Breaker Testing
-
```csharp
[Test]
public async Task Circuit_Breaker_Opens()
@@ -507,7 +480,6 @@ public async Task Circuit_Breaker_Opens()
### Producer-Consumer Testing
-
```csharp
[Test]
public async Task Producer_Consumer_Processes_Items()
@@ -524,7 +496,6 @@ public async Task Producer_Consumer_Processes_Items()
### Rate Limiting
-
```csharp
[Test]
public async Task Rate_Limiter_Delays_Requests()
@@ -546,7 +517,6 @@ public async Task Rate_Limiter_Delays_Requests()
## Testing Async Disposal
-
```csharp
[Test]
public async Task Async_Disposable_Cleanup()
diff --git a/docs/docs/assertions/type-checking.md b/docs/docs/assertions/type-checking.md
index d908b104b0f..1a81b920e50 100644
--- a/docs/docs/assertions/type-checking.md
+++ b/docs/docs/assertions/type-checking.md
@@ -2,7 +2,6 @@
sidebar_position: 5
---
-
# Type Checking
@@ -10,7 +9,6 @@ TUnit assertions check types at compile time wherever possible. This gives faste
For example, this wouldn't compile because we're comparing an `int` and a `string`:
-
```csharp
[Test]
public async Task MyTest()
@@ -27,7 +25,6 @@ When you need to verify types at runtime — for example, when working with poly
Tests that a value is exactly the specified type (not a subclass):
-
```csharp
[Test]
public async Task Exact_Type()
@@ -42,7 +39,6 @@ public async Task Exact_Type()
Tests that a value can be assigned to the specified type, including base classes and interfaces:
-
```csharp
[Test]
public async Task Assignable_To_Base_Or_Interface()
@@ -58,7 +54,6 @@ public async Task Assignable_To_Base_Or_Interface()
Tests that a value is **not** exactly the specified type:
-
```csharp
[Test]
public async Task Not_Exact_Type()
@@ -73,7 +68,6 @@ public async Task Not_Exact_Type()
Tests that a value cannot be assigned to the specified type:
-
```csharp
[Test]
public async Task Not_Assignable()
@@ -88,7 +82,6 @@ public async Task Not_Assignable()
Tests that a value of the specified type can be assigned to a variable of this value's type. This is the reverse of `IsAssignableTo`:
-
```csharp
[Test]
public async Task Assignable_From_Derived()
@@ -104,7 +97,6 @@ public async Task Assignable_From_Derived()
Tests that a value of the specified type cannot be assigned to a variable of this value's type:
-
```csharp
[Test]
public async Task Not_Assignable_From()
@@ -119,7 +111,6 @@ public async Task Not_Assignable_From()
Type assertions also work on delegate return values, letting you verify the type returned by a method or lambda:
-
```csharp
[Test]
public async Task Delegate_Return_Type()
diff --git a/docs/docs/assertions/types.md b/docs/docs/assertions/types.md
index c6f190157b8..7a0efed6ea5 100644
--- a/docs/docs/assertions/types.md
+++ b/docs/docs/assertions/types.md
@@ -2,7 +2,6 @@
sidebar_position: 9
---
-
# Type Assertions
@@ -14,7 +13,6 @@ TUnit provides comprehensive assertions for testing types and type properties. T
Tests that a value is exactly of a specific type:
-
```csharp
[Test]
public async Task Value_Is_Type()
@@ -27,7 +25,6 @@ public async Task Value_Is_Type()
Works with all types:
-
```csharp
[Test]
public async Task Various_Types()
@@ -43,7 +40,6 @@ public async Task Various_Types()
Tests that a type can be assigned to a target type (inheritance/interface):
-
```csharp
[Test]
public async Task Type_Is_Assignable()
@@ -58,7 +54,6 @@ public async Task Type_Is_Assignable()
With inheritance:
-
```csharp
public class Animal { }
public class Dog : Animal { }
@@ -78,7 +73,6 @@ public async Task Inheritance_Assignability()
Tests that a type cannot be assigned to a target type:
-
```csharp
[Test]
public async Task Type_Not_Assignable()
@@ -94,7 +88,6 @@ public async Task Type_Not_Assignable()
All the following assertions work on `Type` objects directly:
-
```csharp
[Test]
public async Task Type_Object_Assertions()
@@ -110,7 +103,6 @@ public async Task Type_Object_Assertions()
#### IsClass / IsNotClass
-
```csharp
[Test]
public async Task Is_Class()
@@ -126,7 +118,6 @@ public async Task Is_Class()
#### IsInterface / IsNotInterface
-
```csharp
[Test]
public async Task Is_Interface()
@@ -142,7 +133,6 @@ public async Task Is_Interface()
#### IsAbstract / IsNotAbstract
-
```csharp
public abstract class AbstractBase { }
public class Concrete : AbstractBase { }
@@ -157,7 +147,6 @@ public async Task Is_Abstract()
#### IsSealed / IsNotSealed
-
```csharp
public sealed class SealedClass { }
public class OpenClass { }
@@ -175,7 +164,6 @@ public async Task Is_Sealed()
#### IsValueType / IsNotValueType
-
```csharp
[Test]
public async Task Is_Value_Type()
@@ -191,7 +179,6 @@ public async Task Is_Value_Type()
#### IsEnum / IsNotEnum
-
```csharp
public enum Color { Red, Green, Blue }
@@ -207,7 +194,6 @@ public async Task Is_Enum()
#### IsPrimitive / IsNotPrimitive
-
```csharp
[Test]
public async Task Is_Primitive()
@@ -227,7 +213,6 @@ public async Task Is_Primitive()
#### IsPublic / IsNotPublic
-
```csharp
public class PublicClass { }
internal class InternalClass { }
@@ -246,7 +231,6 @@ public async Task Is_Public()
#### IsGenericType / IsNotGenericType
-
```csharp
[Test]
public async Task Is_Generic_Type()
@@ -260,7 +244,6 @@ public async Task Is_Generic_Type()
#### IsGenericTypeDefinition / IsNotGenericTypeDefinition
-
```csharp
[Test]
public async Task Is_Generic_Type_Definition()
@@ -276,7 +259,6 @@ public async Task Is_Generic_Type_Definition()
#### IsConstructedGenericType / IsNotConstructedGenericType
-
```csharp
[Test]
public async Task Is_Constructed_Generic_Type()
@@ -291,7 +273,6 @@ public async Task Is_Constructed_Generic_Type()
#### ContainsGenericParameters / DoesNotContainGenericParameters
-
```csharp
[Test]
public async Task Contains_Generic_Parameters()
@@ -307,7 +288,6 @@ public async Task Contains_Generic_Parameters()
#### IsArray / IsNotArray
-
```csharp
[Test]
public async Task Is_Array()
@@ -322,7 +302,6 @@ public async Task Is_Array()
#### IsByRef / IsNotByRef
-
```csharp
[Test]
public async Task Is_By_Ref()
@@ -337,7 +316,6 @@ public async Task Is_By_Ref()
#### IsByRefLike / IsNotByRefLike (.NET 5+)
-
```csharp
[Test]
public async Task Is_By_Ref_Like()
@@ -351,17 +329,17 @@ public async Task Is_By_Ref_Like()
#### IsPointer / IsNotPointer
-
```csharp
[Test]
public async Task Is_Pointer()
{
+ Type intPtr;
unsafe
{
- var intPtr = typeof(int*);
- await Assert.That(intPtr).IsPointer();
+ intPtr = typeof(int*);
}
+ await Assert.That(intPtr).IsPointer();
await Assert.That(typeof(int)).IsNotPointer();
}
```
@@ -370,7 +348,6 @@ public async Task Is_Pointer()
#### IsNested / IsNotNested
-
```csharp
public class Outer
{
@@ -387,7 +364,6 @@ public async Task Is_Nested()
#### IsNestedPublic / IsNotNestedPublic
-
```csharp
public class Container
{
@@ -404,7 +380,6 @@ public async Task Is_Nested_Public()
#### IsNestedPrivate / IsNotNestedPrivate
-
```csharp
[Test]
public async Task Is_Nested_Private()
@@ -428,7 +403,6 @@ For protected nested types.
#### IsVisible / IsNotVisible
-
```csharp
[Test]
public async Task Is_Visible()
@@ -437,7 +411,7 @@ public async Task Is_Visible()
await Assert.That(typeof(List)).IsVisible();
// Internal types are not visible
- var internalType = Assembly.GetExecutingAssembly()
+ var internalType = System.Reflection.Assembly.GetExecutingAssembly()
.GetTypes()
.FirstOrDefault(t => !t.IsPublic && !t.IsNested);
@@ -452,7 +426,6 @@ public async Task Is_Visible()
#### IsCOMObject / IsNotCOMObject
-
```csharp
[Test]
public async Task Is_COM_Object()
@@ -466,7 +439,6 @@ public async Task Is_COM_Object()
### Dependency Injection Validation
-
```csharp
[Test]
public async Task Service_Implements_Interface()
@@ -480,7 +452,6 @@ public async Task Service_Implements_Interface()
### Plugin System
-
```csharp
public interface IPlugin { }
@@ -497,7 +468,6 @@ public async Task Plugin_Implements_Interface()
### Reflection Testing
-
```csharp
[Test]
public async Task Type_Has_Expected_Properties()
@@ -513,7 +483,6 @@ public async Task Type_Has_Expected_Properties()
### Generic Constraints
-
```csharp
[Test]
public async Task Validate_Generic_Constraints()
@@ -527,7 +496,6 @@ public async Task Validate_Generic_Constraints()
### Enum Validation
-
```csharp
[Test]
public async Task Type_Is_Enum()
@@ -541,7 +509,6 @@ public async Task Type_Is_Enum()
### Abstract Class Validation
-
```csharp
[Test]
public async Task Base_Class_Is_Abstract()
@@ -555,7 +522,6 @@ public async Task Base_Class_Is_Abstract()
## Chaining Type Assertions
-
```csharp
[Test]
public async Task Chained_Type_Assertions()
@@ -572,7 +538,6 @@ public async Task Chained_Type_Assertions()
## Type Comparison
-
```csharp
[Test]
public async Task Compare_Types()
@@ -588,7 +553,6 @@ public async Task Compare_Types()
## Working with Base Types
-
```csharp
[Test]
public async Task Check_Base_Type()
@@ -602,7 +566,6 @@ public async Task Check_Base_Type()
## Interface Implementation
-
```csharp
[Test]
public async Task Implements_Multiple_Interfaces()
@@ -619,12 +582,11 @@ public async Task Implements_Multiple_Interfaces()
### Factory Pattern Validation
-
```csharp
[Test]
public async Task Factory_Returns_Correct_Type()
{
- var instance = Factory.Create("user-service");
+ var instance = new ExampleFactory().Create("user-service");
await Assert.That(instance).IsTypeOf();
await Assert.That(instance).IsAssignableTo();
@@ -633,7 +595,6 @@ public async Task Factory_Returns_Correct_Type()
### ORM Entity Validation
-
```csharp
[Test]
public async Task Entity_Is_Properly_Configured()
@@ -651,7 +612,6 @@ public async Task Entity_Is_Properly_Configured()
### Serialization Requirements
-
```csharp
[Test]
public async Task Type_Is_Serializable()
@@ -669,7 +629,6 @@ public async Task Type_Is_Serializable()
### Test Double Validation
-
```csharp
[Test]
public async Task Mock_Implements_Interface()
@@ -683,7 +642,6 @@ public async Task Mock_Implements_Interface()
## Struct Validation
-
```csharp
public struct Point
{
@@ -704,7 +662,6 @@ public async Task Struct_Properties()
## Record Validation
-
```csharp
public record Person(string Name, int Age);
diff --git a/docs/docs/benchmarks/engine/AsyncTests.md b/docs/docs/benchmarks/engine/AsyncTests.md
index 7a4bdd21070..e4aaef0bfd5 100644
--- a/docs/docs/benchmarks/engine/AsyncTests.md
+++ b/docs/docs/benchmarks/engine/AsyncTests.md
@@ -9,7 +9,7 @@ sidebar_position: 3
> Realistic async/await patterns with I/O simulation
:::info Last Updated
-This benchmark was automatically generated on **2026-08-23** from the latest CI run.
+This benchmark was automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -18,12 +18,12 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
| Framework | Version | Mean | Median | StdDev |
|-----------|---------|------|--------|--------|
-| **TUnit** | 1.65.38 | 358.9 ms | 358.6 ms | 2.79 ms |
-| NUnit | 4.6.1 | 577.1 ms | 574.3 ms | 9.26 ms |
-| MSTest | 4.3.3 | 678.0 ms | 673.8 ms | 20.11 ms |
-| xUnit3 | 4.0.0 | 737.0 ms | 733.0 ms | 27.93 ms |
-| **TUnit (AOT)** | 1.65.38 | 118.5 ms | 118.6 ms | 1.06 ms |
-| xUnit3_AOT | 4.0.0 | 120.1 ms | 120.0 ms | 1.23 ms |
+| **TUnit** | 1.65.68 | 388.5 ms | 386.4 ms | 22.97 ms |
+| NUnit | 4.6.1 | 714.3 ms | 707.6 ms | 19.79 ms |
+| MSTest | 4.3.3 | 664.8 ms | 664.0 ms | 5.74 ms |
+| xUnit3 | 4.0.0 | 730.9 ms | 733.0 ms | 16.56 ms |
+| **TUnit (AOT)** | 1.65.68 | 116.0 ms | 116.0 ms | 0.29 ms |
+| xUnit3_AOT | 4.0.0 | 118.5 ms | 118.5 ms | 0.68 ms |
## 📈 Visual Comparison
@@ -61,8 +61,8 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
xychart-beta
title "AsyncTests Performance Comparison"
x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT", "xUnit3_AOT"]
- y-axis "Time (ms)" 0 --> 885
- bar [358.9, 577.1, 678, 737, 118.5, 120.1]
+ y-axis "Time (ms)" 0 --> 878
+ bar [388.5, 714.3, 664.8, 730.9, 116, 118.5]
```
## 🎯 Key Insights
@@ -75,4 +75,4 @@ This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3, xUnit
View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information.
:::
-*Last generated: 2026-08-23T00:20:42.432Z*
+*Last generated: 2026-08-30T00:32:59.981Z*
diff --git a/docs/docs/benchmarks/engine/BuildTime.md b/docs/docs/benchmarks/engine/BuildTime.md
index 6c19b52ec9e..7e06da9343d 100644
--- a/docs/docs/benchmarks/engine/BuildTime.md
+++ b/docs/docs/benchmarks/engine/BuildTime.md
@@ -9,7 +9,7 @@ sidebar_position: 9
> Compilation time from a clean build across frameworks — how long it takes to build an identical test project.
:::info Last Updated
-This benchmark was automatically generated on **2026-08-23** from the latest CI run.
+This benchmark was automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -20,10 +20,10 @@ Compilation time comparison across frameworks:
| Framework | Version | Mean | Median | StdDev |
|-----------|---------|------|--------|--------|
-| **TUnit** | 1.65.38 | 917.6 ms | 918.9 ms | 26.90 ms |
-| Build_NUnit | 4.6.1 | 893.9 ms | 899.1 ms | 12.39 ms |
-| Build_MSTest | 4.3.3 | 1,036.7 ms | 1,037.3 ms | 13.77 ms |
-| Build_xUnit3 | 4.0.0 | 862.9 ms | 861.3 ms | 12.60 ms |
+| **TUnit** | 1.65.68 | 923.9 ms | 915.4 ms | 34.98 ms |
+| Build_NUnit | 4.6.1 | 884.6 ms | 885.1 ms | 9.95 ms |
+| Build_MSTest | 4.3.3 | 1,026.0 ms | 1,019.4 ms | 47.11 ms |
+| Build_xUnit3 | 4.0.0 | 879.1 ms | 881.5 ms | 8.46 ms |
## 📈 Visual Comparison
@@ -61,8 +61,8 @@ Compilation time comparison across frameworks:
xychart-beta
title "Build Time Comparison"
x-axis ["Build_TUnit", "Build_NUnit", "Build_MSTest", "Build_xUnit3"]
- y-axis "Time (ms)" 0 --> 1245
- bar [917.6, 893.9, 1036.7, 862.9]
+ y-axis "Time (ms)" 0 --> 1232
+ bar [923.9, 884.6, 1026, 879.1]
```
---
@@ -71,4 +71,4 @@ xychart-beta
View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information.
:::
-*Last generated: 2026-08-23T00:20:42.433Z*
+*Last generated: 2026-08-30T00:32:59.983Z*
diff --git a/docs/docs/benchmarks/engine/DataDrivenTests.md b/docs/docs/benchmarks/engine/DataDrivenTests.md
index 5a3f44ace64..d779989b434 100644
--- a/docs/docs/benchmarks/engine/DataDrivenTests.md
+++ b/docs/docs/benchmarks/engine/DataDrivenTests.md
@@ -9,7 +9,7 @@ sidebar_position: 4
> Parameterized tests with multiple data sources
:::info Last Updated
-This benchmark was automatically generated on **2026-08-23** from the latest CI run.
+This benchmark was automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -18,12 +18,12 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
| Framework | Version | Mean | Median | StdDev |
|-----------|---------|------|--------|--------|
-| **TUnit** | 1.65.38 | 268.40 ms | 267.52 ms | 2.408 ms |
-| NUnit | 4.6.1 | 498.74 ms | 496.12 ms | 9.573 ms |
-| MSTest | 4.3.3 | 490.02 ms | 489.68 ms | 11.852 ms |
-| xUnit3 | 4.0.0 | 586.26 ms | 585.77 ms | 7.853 ms |
-| **TUnit (AOT)** | 1.65.38 | 13.98 ms | 13.82 ms | 0.551 ms |
-| xUnit3_AOT | 4.0.0 | 16.70 ms | 16.80 ms | 0.338 ms |
+| **TUnit** | 1.65.68 | 281.32 ms | 278.96 ms | 11.233 ms |
+| NUnit | 4.6.1 | 564.15 ms | 561.05 ms | 16.735 ms |
+| MSTest | 4.3.3 | 507.18 ms | 505.65 ms | 12.262 ms |
+| xUnit3 | 4.0.0 | 655.45 ms | 655.21 ms | 27.750 ms |
+| **TUnit (AOT)** | 1.65.68 | 16.57 ms | 16.26 ms | 1.104 ms |
+| xUnit3_AOT | 4.0.0 | 20.29 ms | 20.48 ms | 1.727 ms |
## 📈 Visual Comparison
@@ -61,8 +61,8 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
xychart-beta
title "DataDrivenTests Performance Comparison"
x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT", "xUnit3_AOT"]
- y-axis "Time (ms)" 0 --> 704
- bar [268.4, 498.74, 490.02, 586.26, 13.98, 16.7]
+ y-axis "Time (ms)" 0 --> 787
+ bar [281.32, 564.15, 507.18, 655.45, 16.57, 20.29]
```
## 🎯 Key Insights
@@ -75,4 +75,4 @@ This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3, xUnit
View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information.
:::
-*Last generated: 2026-08-23T00:20:42.432Z*
+*Last generated: 2026-08-30T00:32:59.981Z*
diff --git a/docs/docs/benchmarks/engine/MassiveParallelTests.md b/docs/docs/benchmarks/engine/MassiveParallelTests.md
index 4485192783b..9bb1890ae93 100644
--- a/docs/docs/benchmarks/engine/MassiveParallelTests.md
+++ b/docs/docs/benchmarks/engine/MassiveParallelTests.md
@@ -9,7 +9,7 @@ sidebar_position: 5
> Parallel execution stress tests
:::info Last Updated
-This benchmark was automatically generated on **2026-08-23** from the latest CI run.
+This benchmark was automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -18,12 +18,12 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
| Framework | Version | Mean | Median | StdDev |
|-----------|---------|------|--------|--------|
-| **TUnit** | 1.65.38 | 471.8 ms | 461.6 ms | 18.81 ms |
-| NUnit | 4.6.1 | 1,083.0 ms | 1,079.8 ms | 13.12 ms |
-| MSTest | 4.3.3 | 2,975.1 ms | 2,974.6 ms | 14.65 ms |
-| xUnit3 | 4.0.0 | 1,289.6 ms | 1,278.2 ms | 23.16 ms |
-| **TUnit (AOT)** | 1.65.38 | 218.2 ms | 218.1 ms | 1.08 ms |
-| xUnit3_AOT | 4.0.0 | 673.0 ms | 672.6 ms | 1.97 ms |
+| **TUnit** | 1.65.68 | 535.8 ms | 538.1 ms | 23.04 ms |
+| NUnit | 4.6.1 | 1,317.5 ms | 1,310.7 ms | 34.72 ms |
+| MSTest | 4.3.3 | 3,040.5 ms | 3,027.4 ms | 40.38 ms |
+| xUnit3 | 4.0.0 | 1,337.9 ms | 1,332.9 ms | 45.41 ms |
+| **TUnit (AOT)** | 1.65.68 | 220.9 ms | 221.1 ms | 0.61 ms |
+| xUnit3_AOT | 4.0.0 | 676.7 ms | 677.0 ms | 1.49 ms |
## 📈 Visual Comparison
@@ -61,8 +61,8 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
xychart-beta
title "MassiveParallelTests Performance Comparison"
x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT", "xUnit3_AOT"]
- y-axis "Time (ms)" 0 --> 3571
- bar [471.8, 1083, 2975.1, 1289.6, 218.2, 673]
+ y-axis "Time (ms)" 0 --> 3649
+ bar [535.8, 1317.5, 3040.5, 1337.9, 220.9, 676.7]
```
## 🎯 Key Insights
@@ -75,4 +75,4 @@ This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3, xUnit
View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information.
:::
-*Last generated: 2026-08-23T00:20:42.432Z*
+*Last generated: 2026-08-30T00:32:59.982Z*
diff --git a/docs/docs/benchmarks/engine/MatrixTests.md b/docs/docs/benchmarks/engine/MatrixTests.md
index b430473b6a5..0e70f25bcb5 100644
--- a/docs/docs/benchmarks/engine/MatrixTests.md
+++ b/docs/docs/benchmarks/engine/MatrixTests.md
@@ -9,7 +9,7 @@ sidebar_position: 6
> Combinatorial test generation and execution
:::info Last Updated
-This benchmark was automatically generated on **2026-08-23** from the latest CI run.
+This benchmark was automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -18,12 +18,12 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
| Framework | Version | Mean | Median | StdDev |
|-----------|---------|------|--------|--------|
-| **TUnit** | 1.65.38 | 377.3 ms | 374.3 ms | 13.53 ms |
-| NUnit | 4.6.1 | 1,443.4 ms | 1,438.9 ms | 13.23 ms |
-| MSTest | 4.3.3 | 1,532.2 ms | 1,528.3 ms | 24.49 ms |
-| xUnit3 | 4.0.0 | 960.5 ms | 957.4 ms | 52.75 ms |
-| **TUnit (AOT)** | 1.65.38 | 120.4 ms | 120.5 ms | 1.11 ms |
-| xUnit3_AOT | 4.0.0 | 275.0 ms | 275.1 ms | 1.08 ms |
+| **TUnit** | 1.65.68 | 364.9 ms | 365.0 ms | 1.92 ms |
+| NUnit | 4.6.1 | 1,536.5 ms | 1,538.5 ms | 6.01 ms |
+| MSTest | 4.3.3 | 1,497.2 ms | 1,495.6 ms | 8.02 ms |
+| xUnit3 | 4.0.0 | 861.9 ms | 862.6 ms | 9.53 ms |
+| **TUnit (AOT)** | 1.65.68 | 117.1 ms | 117.1 ms | 1.20 ms |
+| xUnit3_AOT | 4.0.0 | 269.2 ms | 269.0 ms | 0.88 ms |
## 📈 Visual Comparison
@@ -61,8 +61,8 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
xychart-beta
title "MatrixTests Performance Comparison"
x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT", "xUnit3_AOT"]
- y-axis "Time (ms)" 0 --> 1839
- bar [377.3, 1443.4, 1532.2, 960.5, 120.4, 275]
+ y-axis "Time (ms)" 0 --> 1844
+ bar [364.9, 1536.5, 1497.2, 861.9, 117.1, 269.2]
```
## 🎯 Key Insights
@@ -75,4 +75,4 @@ This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3, xUnit
View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information.
:::
-*Last generated: 2026-08-23T00:20:42.432Z*
+*Last generated: 2026-08-30T00:32:59.982Z*
diff --git a/docs/docs/benchmarks/engine/ScaleTests.md b/docs/docs/benchmarks/engine/ScaleTests.md
index ef99fbe6779..42ee291670e 100644
--- a/docs/docs/benchmarks/engine/ScaleTests.md
+++ b/docs/docs/benchmarks/engine/ScaleTests.md
@@ -9,7 +9,7 @@ sidebar_position: 7
> Large test suites (150+ tests) measuring scalability
:::info Last Updated
-This benchmark was automatically generated on **2026-08-23** from the latest CI run.
+This benchmark was automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -18,12 +18,12 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
| Framework | Version | Mean | Median | StdDev |
|-----------|---------|------|--------|--------|
-| **TUnit** | 1.65.38 | 280.02 ms | 279.97 ms | 3.282 ms |
-| NUnit | 4.6.1 | 522.04 ms | 515.34 ms | 22.927 ms |
-| MSTest | 4.3.3 | 505.40 ms | 504.41 ms | 12.702 ms |
-| xUnit3 | 4.0.0 | 620.62 ms | 618.35 ms | 15.701 ms |
-| **TUnit (AOT)** | 1.65.38 | 19.86 ms | 20.26 ms | 2.114 ms |
-| xUnit3_AOT | 4.0.0 | 23.07 ms | 22.85 ms | 0.874 ms |
+| **TUnit** | 1.65.68 | 334.54 ms | 334.15 ms | 24.989 ms |
+| NUnit | 4.6.1 | 643.81 ms | 636.45 ms | 32.013 ms |
+| MSTest | 4.3.3 | 562.39 ms | 560.91 ms | 32.264 ms |
+| xUnit3 | 4.0.0 | 710.91 ms | 708.11 ms | 30.502 ms |
+| **TUnit (AOT)** | 1.65.68 | 18.88 ms | 18.84 ms | 0.550 ms |
+| xUnit3_AOT | 4.0.0 | 23.38 ms | 23.59 ms | 1.044 ms |
## 📈 Visual Comparison
@@ -61,8 +61,8 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
xychart-beta
title "ScaleTests Performance Comparison"
x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT", "xUnit3_AOT"]
- y-axis "Time (ms)" 0 --> 745
- bar [280.02, 522.04, 505.4, 620.62, 19.86, 23.07]
+ y-axis "Time (ms)" 0 --> 854
+ bar [334.54, 643.81, 562.39, 710.91, 18.88, 23.38]
```
## 🎯 Key Insights
@@ -75,4 +75,4 @@ This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3, xUnit
View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information.
:::
-*Last generated: 2026-08-23T00:20:42.433Z*
+*Last generated: 2026-08-30T00:32:59.982Z*
diff --git a/docs/docs/benchmarks/engine/SetupTeardownTests.md b/docs/docs/benchmarks/engine/SetupTeardownTests.md
index 68c88343dbc..f2aae685f29 100644
--- a/docs/docs/benchmarks/engine/SetupTeardownTests.md
+++ b/docs/docs/benchmarks/engine/SetupTeardownTests.md
@@ -9,7 +9,7 @@ sidebar_position: 8
> Expensive test fixtures with setup/teardown overhead
:::info Last Updated
-This benchmark was automatically generated on **2026-08-23** from the latest CI run.
+This benchmark was automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -18,12 +18,12 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
| Framework | Version | Mean | Median | StdDev |
|-----------|---------|------|--------|--------|
-| **TUnit** | 1.65.38 | 389.40 ms | 380.76 ms | 37.638 ms |
-| NUnit | 4.6.1 | 1,090.23 ms | 1,076.47 ms | 46.892 ms |
-| MSTest | 4.3.3 | 1,163.10 ms | 1,135.41 ms | 60.412 ms |
-| xUnit3 | 4.0.0 | 784.00 ms | 785.63 ms | 20.951 ms |
-| **TUnit (AOT)** | 1.65.38 | 70.18 ms | 69.89 ms | 2.527 ms |
-| xUnit3_AOT | 4.0.0 | 179.90 ms | 179.56 ms | 3.249 ms |
+| **TUnit** | 1.65.68 | 367.81 ms | 364.64 ms | 14.446 ms |
+| NUnit | 4.6.1 | 1,270.05 ms | 1,267.62 ms | 52.049 ms |
+| MSTest | 4.3.3 | 1,322.57 ms | 1,319.06 ms | 23.266 ms |
+| xUnit3 | 4.0.0 | 955.39 ms | 959.77 ms | 26.122 ms |
+| **TUnit (AOT)** | 1.65.68 | 75.64 ms | 75.74 ms | 1.348 ms |
+| xUnit3_AOT | 4.0.0 | 182.31 ms | 182.43 ms | 1.503 ms |
## 📈 Visual Comparison
@@ -61,8 +61,8 @@ This benchmark was automatically generated on **2026-08-23** from the latest CI
xychart-beta
title "SetupTeardownTests Performance Comparison"
x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT", "xUnit3_AOT"]
- y-axis "Time (ms)" 0 --> 1396
- bar [389.4, 1090.23, 1163.1, 784, 70.18, 179.9]
+ y-axis "Time (ms)" 0 --> 1588
+ bar [367.81, 1270.05, 1322.57, 955.39, 75.64, 182.31]
```
## 🎯 Key Insights
@@ -75,4 +75,4 @@ This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3, xUnit
View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information.
:::
-*Last generated: 2026-08-23T00:20:42.433Z*
+*Last generated: 2026-08-30T00:32:59.983Z*
diff --git a/docs/docs/benchmarks/index.md b/docs/docs/benchmarks/index.md
index dd94279341d..c975c6a4cd1 100644
--- a/docs/docs/benchmarks/index.md
+++ b/docs/docs/benchmarks/index.md
@@ -7,7 +7,7 @@ sidebar_position: 1
# Performance Benchmarks
:::info Last Updated
-These benchmarks were automatically generated on **2026-08-23** from the latest CI run.
+These benchmarks were automatically generated on **2026-08-30** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -37,7 +37,7 @@ These benchmarks compare TUnit against the most popular .NET testing frameworks:
| Framework | Version Tested |
|-----------|----------------|
-| **TUnit** | 1.65.38 |
+| **TUnit** | 1.65.68 |
| **xUnit v3** | 4.0.0 |
| **NUnit** | 4.6.1 |
| **MSTest** | 4.3.3 |
@@ -80,4 +80,4 @@ These benchmarks run automatically daily via [GitHub Actions](https://github.com
Each benchmark runs multiple iterations with statistical analysis to ensure accuracy. Results may vary based on hardware and test characteristics.
:::
-*Last generated: 2026-08-23T00:20:42.434Z*
+*Last generated: 2026-08-30T00:32:59.983Z*
diff --git a/docs/docs/benchmarks/methodology.md b/docs/docs/benchmarks/methodology.md
index 3907dd66444..720cdc7b1ff 100644
--- a/docs/docs/benchmarks/methodology.md
+++ b/docs/docs/benchmarks/methodology.md
@@ -4,7 +4,6 @@ description: How TUnit's performance benchmarks are measured and compared
sidebar_position: 2
---
-
# Benchmark Methodology
@@ -41,7 +40,6 @@ All benchmarks use [BenchmarkDotNet](https://benchmarkdotnet.org/), the industry
**Purpose**: Measure parameterized test performance
**What's tested**:
-
```csharp
[Test]
[Arguments(1, 2, 3)]
@@ -61,7 +59,6 @@ public async Task TestAddition(int a, int b, int expected)
**Purpose**: Measure async/await pattern performance
**What's tested**:
-
```csharp
[Test]
public async Task TestAsyncOperation()
@@ -91,7 +88,6 @@ public async Task TestAsyncOperation()
**Purpose**: Measure combinatorial test generation
**What's tested**:
-
```csharp
[Test]
[MatrixDataSource]
@@ -160,7 +156,6 @@ dotnet build -c Release -p:TestFramework=MSTEST
```
### 2. Execution Phase
-
```csharp
using BenchmarkDotNet.Attributes;
diff --git a/docs/docs/benchmarks/mocks/Callback.md b/docs/docs/benchmarks/mocks/Callback.md
index b67b1a9998f..b69b63b75e8 100644
--- a/docs/docs/benchmarks/mocks/Callback.md
+++ b/docs/docs/benchmarks/mocks/Callback.md
@@ -9,7 +9,7 @@ sidebar_position: 2
> Callback registration and execution — comparing **TUnit.Mocks** (source-generated) against runtime proxy-based mocking libraries.
:::info Last Updated
-This benchmark was automatically generated on **2026-08-26** from the latest CI run.
+This benchmark was automatically generated on **2026-09-04** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -20,12 +20,12 @@ Callback registration and execution:
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 524.5 ns | 4.74 ns | 3.96 ns | 3.11 KB |
-| Imposter | 377.2 ns | 4.79 ns | 4.48 ns | 2.66 KB |
-| Mockolate | 274.2 ns | 3.07 ns | 2.87 ns | 1.8 KB |
-| Moq | 108,008.1 ns | 672.13 ns | 561.26 ns | 13.29 KB |
-| NSubstitute | 3,564.6 ns | 55.53 ns | 51.95 ns | 7.85 KB |
-| FakeItEasy | 3,801.3 ns | 47.95 ns | 44.85 ns | 7.44 KB |
+| **TUnit.Mocks** | 525.9 ns | 10.29 ns | 10.10 ns | 3.11 KB |
+| Imposter | 356.3 ns | 2.99 ns | 2.79 ns | 2.66 KB |
+| Mockolate | 277.9 ns | 3.23 ns | 3.02 ns | 1.8 KB |
+| Moq | 107,115.3 ns | 524.39 ns | 490.52 ns | 13.29 KB |
+| NSubstitute | 3,576.5 ns | 59.01 ns | 52.32 ns | 7.85 KB |
+| FakeItEasy | 3,780.5 ns | 28.35 ns | 23.67 ns | 7.44 KB |
```mermaid
%%{init: {
@@ -51,8 +51,8 @@ Callback registration and execution:
xychart-beta
title "Callback Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 129610
- bar [524.5, 377.2, 274.2, 108008.1, 3564.6, 3801.3]
+ y-axis "Time (ns)" 0 --> 128539
+ bar [525.9, 356.3, 277.9, 107115.3, 3576.5, 3780.5]
```
---
@@ -61,12 +61,12 @@ xychart-beta
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 626.7 ns | 7.50 ns | 6.65 ns | 3.2 KB |
-| Imposter | 433.0 ns | 1.87 ns | 1.66 ns | 2.82 KB |
-| Mockolate | 312.6 ns | 3.49 ns | 3.27 ns | 1.84 KB |
-| Moq | 114,029.5 ns | 664.83 ns | 589.36 ns | 13.76 KB |
-| NSubstitute | 3,972.7 ns | 74.00 ns | 69.22 ns | 8.41 KB |
-| FakeItEasy | 4,608.1 ns | 91.72 ns | 94.19 ns | 9.26 KB |
+| **TUnit.Mocks** | 600.6 ns | 3.98 ns | 3.53 ns | 3.2 KB |
+| Imposter | 434.1 ns | 2.38 ns | 2.11 ns | 2.82 KB |
+| Mockolate | 305.6 ns | 2.68 ns | 2.51 ns | 1.84 KB |
+| Moq | 114,961.5 ns | 454.49 ns | 402.89 ns | 13.76 KB |
+| NSubstitute | 3,942.0 ns | 37.84 ns | 35.40 ns | 8.41 KB |
+| FakeItEasy | 4,552.1 ns | 61.89 ns | 54.86 ns | 9.26 KB |
```mermaid
%%{init: {
@@ -92,8 +92,8 @@ xychart-beta
xychart-beta
title "Callback (with args) Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 136836
- bar [626.7, 433, 312.6, 114029.5, 3972.7, 4608.1]
+ y-axis "Time (ns)" 0 --> 137954
+ bar [600.6, 434.1, 305.6, 114961.5, 3942, 4552.1]
```
## 🎯 Key Insights
@@ -106,4 +106,4 @@ This benchmark compares **TUnit.Mocks** (source-generated) against runtime proxy
View the [mock benchmarks overview](/docs/benchmarks/mocks) for methodology details and environment information.
:::
-*Last generated: 2026-08-26T02:57:20.474Z*
+*Last generated: 2026-09-04T02:33:16.366Z*
diff --git a/docs/docs/benchmarks/mocks/CombinedWorkflow.md b/docs/docs/benchmarks/mocks/CombinedWorkflow.md
index 318de9e6dfe..497bc3d22dd 100644
--- a/docs/docs/benchmarks/mocks/CombinedWorkflow.md
+++ b/docs/docs/benchmarks/mocks/CombinedWorkflow.md
@@ -9,7 +9,7 @@ sidebar_position: 3
> Full workflow: create → setup → invoke → verify — comparing **TUnit.Mocks** (source-generated) against runtime proxy-based mocking libraries.
:::info Last Updated
-This benchmark was automatically generated on **2026-08-26** from the latest CI run.
+This benchmark was automatically generated on **2026-09-04** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -20,12 +20,12 @@ Full workflow: create → setup → invoke → verify:
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 2.005 μs | 0.0324 μs | 0.0287 μs | 6.23 KB |
-| Imposter | 2.783 μs | 0.0556 μs | 0.1002 μs | 15.71 KB |
-| Mockolate | 1.790 μs | 0.0342 μs | 0.0380 μs | 7.36 KB |
-| Moq | 303.325 μs | 3.7601 μs | 3.3332 μs | 36.3 KB |
-| NSubstitute | 18.320 μs | 0.1262 μs | 0.1180 μs | 26.72 KB |
-| FakeItEasy | 16.434 μs | 0.2877 μs | 0.2550 μs | 25.52 KB |
+| **TUnit.Mocks** | 1.890 μs | 0.0200 μs | 0.0187 μs | 6.23 KB |
+| Imposter | 2.892 μs | 0.0578 μs | 0.0540 μs | 15.71 KB |
+| Mockolate | 1.680 μs | 0.0194 μs | 0.0172 μs | 7.36 KB |
+| Moq | 404.656 μs | 2.0986 μs | 1.8603 μs | 36.49 KB |
+| NSubstitute | 19.260 μs | 0.0823 μs | 0.0770 μs | 26.72 KB |
+| FakeItEasy | 19.347 μs | 0.1678 μs | 0.1488 μs | 25.85 KB |
```mermaid
%%{init: {
@@ -51,8 +51,8 @@ Full workflow: create → setup → invoke → verify:
xychart-beta
title "CombinedWorkflow Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (μs)" 0 --> 364
- bar [2.005, 2.783, 1.79, 303.325, 18.32, 16.434]
+ y-axis "Time (μs)" 0 --> 486
+ bar [1.89, 2.892, 1.68, 404.656, 19.26, 19.347]
```
## 🎯 Key Insights
@@ -65,4 +65,4 @@ This benchmark compares **TUnit.Mocks** (source-generated) against runtime proxy
View the [mock benchmarks overview](/docs/benchmarks/mocks) for methodology details and environment information.
:::
-*Last generated: 2026-08-26T02:57:20.474Z*
+*Last generated: 2026-09-04T02:33:16.366Z*
diff --git a/docs/docs/benchmarks/mocks/Invocation.md b/docs/docs/benchmarks/mocks/Invocation.md
index 601398ba407..c89f4913df7 100644
--- a/docs/docs/benchmarks/mocks/Invocation.md
+++ b/docs/docs/benchmarks/mocks/Invocation.md
@@ -9,7 +9,7 @@ sidebar_position: 4
> Calling methods on mock objects — comparing **TUnit.Mocks** (source-generated) against runtime proxy-based mocking libraries.
:::info Last Updated
-This benchmark was automatically generated on **2026-08-26** from the latest CI run.
+This benchmark was automatically generated on **2026-09-04** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -20,12 +20,12 @@ Calling methods on mock objects:
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 276.9 ns | 69.53 ns | 3.81 ns | 128 B |
-| Imposter | 298.5 ns | 69.70 ns | 3.82 ns | 168 B |
-| Mockolate | 111.1 ns | 16.94 ns | 0.93 ns | 84 B |
-| Moq | 810.2 ns | 395.18 ns | 21.66 ns | 376 B |
-| NSubstitute | 749.4 ns | 613.64 ns | 33.64 ns | 304 B |
-| FakeItEasy | 1,833.6 ns | 350.91 ns | 19.23 ns | 944 B |
+| **TUnit.Mocks** | 276.11 ns | 61.98 ns | 3.397 ns | 128 B |
+| Imposter | 303.36 ns | 87.10 ns | 4.774 ns | 168 B |
+| Mockolate | 120.36 ns | 50.28 ns | 2.756 ns | 84 B |
+| Moq | 813.27 ns | 76.74 ns | 4.206 ns | 376 B |
+| NSubstitute | 710.74 ns | 172.33 ns | 9.446 ns | 304 B |
+| FakeItEasy | 1,738.63 ns | 161.03 ns | 8.826 ns | 944 B |
```mermaid
%%{init: {
@@ -51,8 +51,8 @@ Calling methods on mock objects:
xychart-beta
title "Invocation Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 2201
- bar [276.9, 298.5, 111.1, 810.2, 749.4, 1833.6]
+ y-axis "Time (ns)" 0 --> 2087
+ bar [276.11, 303.36, 120.36, 813.27, 710.74, 1738.63]
```
---
@@ -61,12 +61,12 @@ xychart-beta
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 166.9 ns | 87.98 ns | 4.82 ns | 96 B |
-| Imposter | 303.1 ns | 55.19 ns | 3.03 ns | 168 B |
-| Mockolate | 100.8 ns | 71.99 ns | 3.95 ns | 60 B |
-| Moq | 564.4 ns | 298.13 ns | 16.34 ns | 296 B |
-| NSubstitute | 656.3 ns | 101.12 ns | 5.54 ns | 328 B |
-| FakeItEasy | 1,623.5 ns | 304.96 ns | 16.72 ns | 776 B |
+| **TUnit.Mocks** | 167.11 ns | 74.09 ns | 4.061 ns | 96 B |
+| Imposter | 291.19 ns | 92.03 ns | 5.045 ns | 168 B |
+| Mockolate | 93.41 ns | 22.35 ns | 1.225 ns | 60 B |
+| Moq | 532.70 ns | 81.24 ns | 4.453 ns | 296 B |
+| NSubstitute | 602.82 ns | 102.70 ns | 5.629 ns | 272 B |
+| FakeItEasy | 1,545.15 ns | 591.79 ns | 32.438 ns | 776 B |
```mermaid
%%{init: {
@@ -92,8 +92,8 @@ xychart-beta
xychart-beta
title "Invocation (String) Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 1949
- bar [166.9, 303.1, 100.8, 564.4, 656.3, 1623.5]
+ y-axis "Time (ns)" 0 --> 1855
+ bar [167.11, 291.19, 93.41, 532.7, 602.82, 1545.15]
```
---
@@ -102,12 +102,12 @@ xychart-beta
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 27,342.5 ns | 10,466.71 ns | 573.72 ns | 12736 B |
-| Imposter | 29,495.1 ns | 10,705.06 ns | 586.78 ns | 16800 B |
-| Mockolate | 10,825.3 ns | 2,802.84 ns | 153.63 ns | 8400 B |
-| Moq | 83,855.2 ns | 24,978.27 ns | 1,369.14 ns | 37600 B |
-| NSubstitute | 81,404.3 ns | 34,696.37 ns | 1,901.82 ns | 36448 B |
-| FakeItEasy | 182,322.3 ns | 55,668.73 ns | 3,051.39 ns | 94400 B |
+| **TUnit.Mocks** | 27,240.28 ns | 9,886.83 ns | 541.931 ns | 12736 B |
+| Imposter | 29,050.40 ns | 6,147.02 ns | 336.939 ns | 16800 B |
+| Mockolate | 10,561.82 ns | 4,525.93 ns | 248.081 ns | 8400 B |
+| Moq | 79,428.50 ns | 6,454.62 ns | 353.799 ns | 37600 B |
+| NSubstitute | 70,130.53 ns | 9,730.73 ns | 533.374 ns | 30848 B |
+| FakeItEasy | 173,430.43 ns | 34,832.13 ns | 1,909.267 ns | 94400 B |
```mermaid
%%{init: {
@@ -133,8 +133,8 @@ xychart-beta
xychart-beta
title "Invocation (100 calls) Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 218787
- bar [27342.5, 29495.1, 10825.3, 83855.2, 81404.3, 182322.3]
+ y-axis "Time (ns)" 0 --> 208117
+ bar [27240.28, 29050.4, 10561.82, 79428.5, 70130.53, 173430.43]
```
## 🎯 Key Insights
@@ -147,4 +147,4 @@ This benchmark compares **TUnit.Mocks** (source-generated) against runtime proxy
View the [mock benchmarks overview](/docs/benchmarks/mocks) for methodology details and environment information.
:::
-*Last generated: 2026-08-26T02:57:20.474Z*
+*Last generated: 2026-09-04T02:33:16.366Z*
diff --git a/docs/docs/benchmarks/mocks/MockCreation.md b/docs/docs/benchmarks/mocks/MockCreation.md
index 4c0dc745150..d7d363e1a32 100644
--- a/docs/docs/benchmarks/mocks/MockCreation.md
+++ b/docs/docs/benchmarks/mocks/MockCreation.md
@@ -9,7 +9,7 @@ sidebar_position: 5
> Mock instance creation performance — comparing **TUnit.Mocks** (source-generated) against runtime proxy-based mocking libraries.
:::info Last Updated
-This benchmark was automatically generated on **2026-08-26** from the latest CI run.
+This benchmark was automatically generated on **2026-09-04** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -20,12 +20,12 @@ Mock instance creation performance:
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 15.770 ns | 0.2187 ns | 0.2045 ns | 200 B |
-| Imposter | 52.184 ns | 0.4922 ns | 0.4363 ns | 440 B |
-| Mockolate | 9.503 ns | 0.2236 ns | 0.2486 ns | 160 B |
-| Moq | 745.059 ns | 10.3641 ns | 9.6946 ns | 2048 B |
-| NSubstitute | 937.076 ns | 11.4854 ns | 10.7434 ns | 5000 B |
-| FakeItEasy | 995.678 ns | 13.9343 ns | 13.0341 ns | 2714 B |
+| **TUnit.Mocks** | 23.40 ns | 0.224 ns | 0.199 ns | 200 B |
+| Imposter | 79.96 ns | 0.386 ns | 0.342 ns | 440 B |
+| Mockolate | 13.99 ns | 0.094 ns | 0.088 ns | 160 B |
+| Moq | 1,010.23 ns | 15.006 ns | 14.036 ns | 2048 B |
+| NSubstitute | 1,452.00 ns | 14.914 ns | 13.950 ns | 5000 B |
+| FakeItEasy | 1,445.48 ns | 28.528 ns | 51.442 ns | 2715 B |
```mermaid
%%{init: {
@@ -51,8 +51,8 @@ Mock instance creation performance:
xychart-beta
title "MockCreation Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 1195
- bar [15.77, 52.184, 9.503, 745.059, 937.076, 995.678]
+ y-axis "Time (ns)" 0 --> 1743
+ bar [23.4, 79.96, 13.99, 1010.23, 1452, 1445.48]
```
---
@@ -61,12 +61,12 @@ xychart-beta
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 16.442 ns | 0.2587 ns | 0.2420 ns | 200 B |
-| Imposter | 81.616 ns | 0.7949 ns | 0.7046 ns | 696 B |
-| Mockolate | 9.639 ns | 0.1504 ns | 0.1407 ns | 176 B |
-| Moq | 694.943 ns | 13.4170 ns | 13.7783 ns | 1912 B |
-| NSubstitute | 935.775 ns | 7.6325 ns | 7.1394 ns | 5000 B |
-| FakeItEasy | 1,006.596 ns | 7.6581 ns | 6.3949 ns | 2714 B |
+| **TUnit.Mocks** | 23.46 ns | 0.239 ns | 0.223 ns | 200 B |
+| Imposter | 123.02 ns | 0.805 ns | 0.753 ns | 696 B |
+| Mockolate | 14.15 ns | 0.129 ns | 0.115 ns | 176 B |
+| Moq | 959.91 ns | 9.791 ns | 9.159 ns | 1912 B |
+| NSubstitute | 1,385.63 ns | 27.592 ns | 35.877 ns | 5000 B |
+| FakeItEasy | 1,287.78 ns | 23.041 ns | 20.425 ns | 2715 B |
```mermaid
%%{init: {
@@ -92,8 +92,8 @@ xychart-beta
xychart-beta
title "MockCreation (Repository) Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 1208
- bar [16.442, 81.616, 9.639, 694.943, 935.775, 1006.596]
+ y-axis "Time (ns)" 0 --> 1663
+ bar [23.46, 123.02, 14.15, 959.91, 1385.63, 1287.78]
```
## 🎯 Key Insights
@@ -106,4 +106,4 @@ This benchmark compares **TUnit.Mocks** (source-generated) against runtime proxy
View the [mock benchmarks overview](/docs/benchmarks/mocks) for methodology details and environment information.
:::
-*Last generated: 2026-08-26T02:57:20.474Z*
+*Last generated: 2026-09-04T02:33:16.366Z*
diff --git a/docs/docs/benchmarks/mocks/Setup.md b/docs/docs/benchmarks/mocks/Setup.md
index a90e10c49f3..363f9b9ec98 100644
--- a/docs/docs/benchmarks/mocks/Setup.md
+++ b/docs/docs/benchmarks/mocks/Setup.md
@@ -9,7 +9,7 @@ sidebar_position: 6
> Mock behavior configuration (returns, matchers) — comparing **TUnit.Mocks** (source-generated) against runtime proxy-based mocking libraries.
:::info Last Updated
-This benchmark was automatically generated on **2026-08-26** from the latest CI run.
+This benchmark was automatically generated on **2026-09-04** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -20,12 +20,12 @@ Mock behavior configuration (returns, matchers):
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 551.7 ns | 10.96 ns | 21.89 ns | 2.34 KB |
-| Imposter | 851.7 ns | 16.20 ns | 24.25 ns | 6.12 KB |
-| Mockolate | 331.7 ns | 6.55 ns | 10.19 ns | 1.41 KB |
-| Moq | 433,988.1 ns | 3,886.68 ns | 3,635.60 ns | 28.68 KB |
-| NSubstitute | 6,263.8 ns | 76.62 ns | 63.98 ns | 9.01 KB |
-| FakeItEasy | 8,319.2 ns | 152.28 ns | 142.44 ns | 10.45 KB |
+| **TUnit.Mocks** | 421.5 ns | 7.07 ns | 5.90 ns | 2.34 KB |
+| Imposter | 665.9 ns | 13.34 ns | 25.05 ns | 6.12 KB |
+| Mockolate | 255.6 ns | 4.02 ns | 3.36 ns | 1.41 KB |
+| Moq | 159,727.2 ns | 2,598.80 ns | 2,552.37 ns | 28.61 KB |
+| NSubstitute | 4,769.0 ns | 93.76 ns | 87.71 ns | 9.01 KB |
+| FakeItEasy | 4,569.9 ns | 88.62 ns | 118.30 ns | 10.44 KB |
```mermaid
%%{init: {
@@ -51,8 +51,8 @@ Mock behavior configuration (returns, matchers):
xychart-beta
title "Setup Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 520786
- bar [551.7, 851.7, 331.7, 433988.1, 6263.8, 8319.2]
+ y-axis "Time (ns)" 0 --> 191673
+ bar [421.5, 665.9, 255.6, 159727.2, 4769, 4569.9]
```
---
@@ -61,12 +61,12 @@ xychart-beta
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 774.0 ns | 15.36 ns | 21.53 ns | 3.15 KB |
-| Imposter | 1,444.0 ns | 22.32 ns | 18.63 ns | 10.59 KB |
-| Mockolate | 548.7 ns | 10.31 ns | 9.64 ns | 2.35 KB |
-| Moq | 114,312.6 ns | 812.13 ns | 719.93 ns | 16.53 KB |
-| NSubstitute | 12,314.6 ns | 76.22 ns | 63.64 ns | 20.31 KB |
-| FakeItEasy | 7,900.2 ns | 127.71 ns | 113.21 ns | 11.71 KB |
+| **TUnit.Mocks** | 674.2 ns | 12.98 ns | 16.41 ns | 3.15 KB |
+| Imposter | 1,087.0 ns | 21.54 ns | 28.75 ns | 10.59 KB |
+| Mockolate | 440.9 ns | 6.52 ns | 5.78 ns | 2.35 KB |
+| Moq | 42,070.2 ns | 453.37 ns | 378.58 ns | 16.52 KB |
+| NSubstitute | 8,155.3 ns | 161.65 ns | 315.28 ns | 20.66 KB |
+| FakeItEasy | 4,233.0 ns | 83.31 ns | 129.70 ns | 11.7 KB |
```mermaid
%%{init: {
@@ -92,8 +92,8 @@ xychart-beta
xychart-beta
title "Setup (Multiple) Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 137176
- bar [774, 1444, 548.7, 114312.6, 12314.6, 7900.2]
+ y-axis "Time (ns)" 0 --> 50485
+ bar [674.2, 1087, 440.9, 42070.2, 8155.3, 4233]
```
## 🎯 Key Insights
@@ -106,4 +106,4 @@ This benchmark compares **TUnit.Mocks** (source-generated) against runtime proxy
View the [mock benchmarks overview](/docs/benchmarks/mocks) for methodology details and environment information.
:::
-*Last generated: 2026-08-26T02:57:20.474Z*
+*Last generated: 2026-09-04T02:33:16.366Z*
diff --git a/docs/docs/benchmarks/mocks/Verification.md b/docs/docs/benchmarks/mocks/Verification.md
index 949c31438b8..76b6092134d 100644
--- a/docs/docs/benchmarks/mocks/Verification.md
+++ b/docs/docs/benchmarks/mocks/Verification.md
@@ -9,7 +9,7 @@ sidebar_position: 7
> Verifying mock method calls — comparing **TUnit.Mocks** (source-generated) against runtime proxy-based mocking libraries.
:::info Last Updated
-This benchmark was automatically generated on **2026-08-26** from the latest CI run.
+This benchmark was automatically generated on **2026-09-04** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -20,12 +20,12 @@ Verifying mock method calls:
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 760.75 ns | 4.092 ns | 3.828 ns | 3008 B |
-| Imposter | 680.80 ns | 5.407 ns | 4.793 ns | 4688 B |
-| Mockolate | 398.57 ns | 0.992 ns | 0.829 ns | 2128 B |
-| Moq | 240,480.10 ns | 1,310.717 ns | 1,161.917 ns | 24324 B |
-| NSubstitute | 6,464.94 ns | 50.175 ns | 41.898 ns | 10064 B |
-| FakeItEasy | 6,411.13 ns | 29.251 ns | 25.930 ns | 10722 B |
+| **TUnit.Mocks** | 996.63 ns | 11.722 ns | 10.965 ns | 3008 B |
+| Imposter | 1,029.90 ns | 14.239 ns | 12.622 ns | 4688 B |
+| Mockolate | 582.88 ns | 7.604 ns | 7.113 ns | 2128 B |
+| Moq | 256,197.99 ns | 1,808.975 ns | 1,603.609 ns | 24306 B |
+| NSubstitute | 7,438.36 ns | 48.396 ns | 42.902 ns | 10064 B |
+| FakeItEasy | 7,377.24 ns | 43.861 ns | 38.882 ns | 10731 B |
```mermaid
%%{init: {
@@ -51,8 +51,8 @@ Verifying mock method calls:
xychart-beta
title "Verification Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 288577
- bar [760.75, 680.8, 398.57, 240480.1, 6464.94, 6411.13]
+ y-axis "Time (ns)" 0 --> 307438
+ bar [996.63, 1029.9, 582.88, 256197.99, 7438.36, 7377.24]
```
---
@@ -61,12 +61,12 @@ xychart-beta
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 55.52 ns | 0.206 ns | 0.183 ns | 320 B |
-| Imposter | 335.09 ns | 0.901 ns | 0.753 ns | 2400 B |
-| Mockolate | 243.15 ns | 0.491 ns | 0.435 ns | 1144 B |
-| Moq | 61,824.78 ns | 234.600 ns | 195.902 ns | 6925 B |
-| NSubstitute | 3,588.04 ns | 13.947 ns | 12.363 ns | 7088 B |
-| FakeItEasy | 3,258.96 ns | 49.439 ns | 46.246 ns | 5210 B |
+| **TUnit.Mocks** | 71.58 ns | 1.433 ns | 1.962 ns | 320 B |
+| Imposter | 471.49 ns | 6.386 ns | 5.974 ns | 2400 B |
+| Mockolate | 316.54 ns | 6.311 ns | 8.638 ns | 1144 B |
+| Moq | 67,662.70 ns | 422.716 ns | 374.727 ns | 6925 B |
+| NSubstitute | 3,982.31 ns | 27.547 ns | 25.767 ns | 7088 B |
+| FakeItEasy | 3,817.50 ns | 34.006 ns | 30.145 ns | 5299 B |
```mermaid
%%{init: {
@@ -92,8 +92,8 @@ xychart-beta
xychart-beta
title "Verification (Never) Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 74190
- bar [55.52, 335.09, 243.15, 61824.78, 3588.04, 3258.96]
+ y-axis "Time (ns)" 0 --> 81196
+ bar [71.58, 471.49, 316.54, 67662.7, 3982.31, 3817.5]
```
---
@@ -102,12 +102,12 @@ xychart-beta
| Library | Mean | Error | StdDev | Allocated |
|---------|------|-------|--------|-----------|
-| **TUnit.Mocks** | 1,261.68 ns | 2.316 ns | 2.167 ns | 4472 B |
-| Imposter | 1,660.58 ns | 5.565 ns | 4.933 ns | 11192 B |
-| Mockolate | 1,137.61 ns | 3.246 ns | 3.036 ns | 5240 B |
-| Moq | 350,973.61 ns | 2,881.199 ns | 2,695.076 ns | 34699 B |
-| NSubstitute | 11,253.45 ns | 35.831 ns | 29.920 ns | 16762 B |
-| FakeItEasy | 11,742.28 ns | 65.267 ns | 61.051 ns | 19344 B |
+| **TUnit.Mocks** | 1,638.65 ns | 14.438 ns | 13.506 ns | 4472 B |
+| Imposter | 2,347.77 ns | 46.412 ns | 78.812 ns | 11192 B |
+| Mockolate | 1,414.07 ns | 24.072 ns | 22.517 ns | 5240 B |
+| Moq | 356,518.29 ns | 2,143.733 ns | 1,900.364 ns | 34814 B |
+| NSubstitute | 12,792.53 ns | 64.138 ns | 56.857 ns | 16762 B |
+| FakeItEasy | 13,248.07 ns | 33.295 ns | 29.516 ns | 19238 B |
```mermaid
%%{init: {
@@ -133,8 +133,8 @@ xychart-beta
xychart-beta
title "Verification (Multiple) Performance Comparison"
x-axis ["TUnit.Mocks", "Imposter", "Mockolate", "Moq", "NSubstitute", "FakeItEasy"]
- y-axis "Time (ns)" 0 --> 421169
- bar [1261.68, 1660.58, 1137.61, 350973.61, 11253.45, 11742.28]
+ y-axis "Time (ns)" 0 --> 427822
+ bar [1638.65, 2347.77, 1414.07, 356518.29, 12792.53, 13248.07]
```
## 🎯 Key Insights
@@ -147,4 +147,4 @@ This benchmark compares **TUnit.Mocks** (source-generated) against runtime proxy
View the [mock benchmarks overview](/docs/benchmarks/mocks) for methodology details and environment information.
:::
-*Last generated: 2026-08-26T02:57:20.474Z*
+*Last generated: 2026-09-04T02:33:16.366Z*
diff --git a/docs/docs/benchmarks/mocks/index.md b/docs/docs/benchmarks/mocks/index.md
index 24cd9f8eb78..60d01b85118 100644
--- a/docs/docs/benchmarks/mocks/index.md
+++ b/docs/docs/benchmarks/mocks/index.md
@@ -7,7 +7,7 @@ sidebar_position: 4
# Mock Library Benchmarks
:::info Last Updated
-These benchmarks were automatically generated on **2026-08-26** from the latest CI run.
+These benchmarks were automatically generated on **2026-09-04** from the latest CI run.
**Environment:** Ubuntu Latest • .NET SDK 10.0.400
:::
@@ -51,7 +51,7 @@ Each benchmark category tests a specific aspect of mocking library usage:
- **Tool**: BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
- **OS**: Ubuntu Latest (GitHub Actions)
-- **Runtime**: .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v3
+- **Runtime**: .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v4
- **Statistical Rigor**: Multiple iterations with warm-up and outlier detection
- **Memory**: Allocation tracking enabled via `[MemoryDiagnoser]`
@@ -76,4 +76,4 @@ These benchmarks run automatically daily via [GitHub Actions](https://github.com
Each benchmark runs multiple iterations with statistical analysis to ensure accuracy. Results may vary based on hardware and test characteristics.
:::
-*Last generated: 2026-08-26T02:57:20.474Z*
+*Last generated: 2026-09-04T02:33:16.366Z*
diff --git a/docs/docs/comparison/framework-differences.md b/docs/docs/comparison/framework-differences.md
index 838f84a579a..d7091d8a2e8 100644
--- a/docs/docs/comparison/framework-differences.md
+++ b/docs/docs/comparison/framework-differences.md
@@ -1,4 +1,3 @@
-
# Framework Differences
@@ -45,11 +44,11 @@ In TUnit, you can inject a `TestContext` into your teardown method, or call `Tes
xUnit assertions have the classic problem of unclear argument order:
-
```csharp
-var one = 2;
-Assert.Equal(1, one); // is 1 the expected or actual?
-Assert.Equal(one, 1); // ...or is it this way round?
+var one = 1;
+var anotherOne = 1;
+Xunit.Assert.Equal(one, anotherOne); // which variable is expected?
+Xunit.Assert.Equal(anotherOne, one); // ...or is it this way round?
```
TUnit uses a fluent syntax that reads naturally: `await Assert.That(one).IsEqualTo(1);`
@@ -98,7 +97,6 @@ In other frameworks, running tests in a specific order usually means disabling p
TUnit has `[DependsOn(...)]` — a test waits for its dependencies to finish, without disabling parallelism for everything else:
-
```csharp
[Test]
public async Task Test1() { ... }
diff --git a/docs/docs/examples/aspire.md b/docs/docs/examples/aspire.md
index 3962b0d8393..ad72ddc13a2 100644
--- a/docs/docs/examples/aspire.md
+++ b/docs/docs/examples/aspire.md
@@ -1,4 +1,4 @@
-
+
# Aspire Integration Testing
@@ -127,10 +127,10 @@ Use `Shared = SharedType.PerTestSession` to start the Aspire app once and share
```csharp
[ClassDataSource(Shared = SharedType.PerTestSession)]
-public class OrderTests(AppFixture fixture) { /* ... */ }
+public class OrderTests(AppFixture fixture) { private AppFixture Fixture { get; } = fixture; }
[ClassDataSource(Shared = SharedType.PerTestSession)]
-public class ProductTests(AppFixture fixture) { /* ... */ }
+public class ProductTests(AppFixture fixture) { private AppFixture Fixture { get; } = fixture; }
// Both test classes share the same AppFixture instance
```
@@ -141,16 +141,19 @@ This is the recommended approach since starting an Aspire distributed applicatio
By default, the fixture waits for **all resources to become healthy** before tests run. You can customize this:
```csharp
-public class AppFixture : AspireFixture
+public class AllRunningAppFixture : AspireFixture
{
- // Option 1: Change the wait behavior via property
protected override ResourceWaitBehavior WaitBehavior => ResourceWaitBehavior.AllRunning;
+}
- // Option 2: Wait for specific resources only
+public class NamedResourcesAppFixture : AspireFixture
+{
protected override ResourceWaitBehavior WaitBehavior => ResourceWaitBehavior.Named;
protected override IEnumerable ResourcesToWaitFor() => ["apiservice", "worker"];
+}
- // Option 3: Full control over the waiting logic
+public class CustomWaitAppFixture : AspireFixture
+{
protected override async Task WaitForResourcesAsync(
DistributedApplication app, CancellationToken cancellationToken)
{
@@ -464,7 +467,8 @@ public class RedisFixture : IAsyncInitializer, IAsyncDisposable
public async Task InitializeAsync()
{
- var connectionString = await App.GetConnectionStringAsync("redis");
+ var connectionString = await App.GetConnectionStringAsync("redis")
+ ?? throw new InvalidOperationException("Redis did not provide a connection string.");
Connection = await ConnectionMultiplexer.ConnectAsync(connectionString);
}
@@ -624,7 +628,7 @@ Use `App` to access the full `DistributedApplication`, then get services or conn
var notifications = fixture.App.Services.GetRequiredService();
// Connection strings
-var connStr = await fixture.GetConnectionStringAsync("postgresdb");
+var connStr = await fixture.GetConnectionStringAsync("postgresdb") ?? throw new InvalidOperationException("Missing postgresdb connection string");
```
### Can I run different AppHosts in different test classes?
@@ -636,10 +640,10 @@ public class AppAFixture : AspireFixture { }
public class AppBFixture : AspireFixture { }
[ClassDataSource(Shared = SharedType.PerTestSession)]
-public class AppATests(AppAFixture fixture) { /* ... */ }
+public class AppATests(AppAFixture fixture) { private AppAFixture Fixture { get; } = fixture; }
[ClassDataSource(Shared = SharedType.PerTestSession)]
-public class AppBTests(AppBFixture fixture) { /* ... */ }
+public class AppBTests(AppBFixture fixture) { private AppBFixture Fixture { get; } = fixture; }
```
### How do I skip waiting for tool containers?
@@ -678,7 +682,10 @@ If a resource stays in `Running` but never reaches `Healthy`, check:
If the resource doesn't have health checks, use `AllRunning` instead of `AllHealthy`:
```csharp
-protected override ResourceWaitBehavior WaitBehavior => ResourceWaitBehavior.AllRunning;
+public class RunningResourcesAppFixture : AspireFixture
+{
+ protected override ResourceWaitBehavior WaitBehavior => ResourceWaitBehavior.AllRunning;
+}
```
### What's the difference between TUnit.Aspire and TUnit.AspNetCore?
diff --git a/docs/docs/examples/aspnet.md b/docs/docs/examples/aspnet.md
index 7dd8c97b653..093481194dd 100644
--- a/docs/docs/examples/aspnet.md
+++ b/docs/docs/examples/aspnet.md
@@ -1,4 +1,3 @@
-
# ASP.NET Core Integration Testing
@@ -301,7 +300,7 @@ var dotPrefix = GetIsolatedPrefix("."); // Returns "test.42."
```csharp
public class InMemoryDatabase : IAsyncInitializer, IAsyncDisposable
{
- public PostgreSqlContainer Container { get; } = new PostgreSqlBuilder()
+ public PostgreSqlContainer Container { get; } = new PostgreSqlBuilder("postgres:18")
.WithImage("postgres:16-alpine")
.Build();
@@ -375,7 +374,9 @@ public class TodoDbContext : DbContext
// IConfiguration is optional: resolved via DI in the app, absent when
// constructing standalone (e.g. in SetupAsync for EnsureCreatedAsync).
- public TodoDbContext(DbContextOptions options, IConfiguration? config = null)
+ public TodoDbContext(
+ DbContextOptions options,
+ Microsoft.Extensions.Configuration.IConfiguration? config = null)
: base(options)
{
SchemaName = config?["Database:Schema"] ?? "public";
@@ -469,10 +470,10 @@ Capture and inspect HTTP requests/responses for assertions:
```csharp
public class CaptureTests : TestsBase
{
- protected override WebApplicationTestOptions Options => new()
+ protected override void ConfigureTestOptions(WebApplicationTestOptions options)
{
- EnableHttpExchangeCapture = true
- };
+ options.EnableHttpExchangeCapture = true;
+ }
[Test]
public async Task RequestIsCaptured()
@@ -500,10 +501,10 @@ using TUnit.AspNetCore.Interception;
public class CaptureTests : TestsBase
{
- protected override WebApplicationTestOptions Options => new()
+ protected override void ConfigureTestOptions(WebApplicationTestOptions options)
{
- EnableHttpExchangeCapture = true
- };
+ options.EnableHttpExchangeCapture = true;
+ }
protected override void ConfigureTestServices(IServiceCollection services)
{
@@ -604,10 +605,10 @@ dotnet add package TUnit.Logging.Microsoft
using TUnit.Logging.Microsoft;
// Via ILoggingBuilder
-builder.Logging.AddTUnit(TestContext.Current!);
+TUnit.Logging.Microsoft.LoggingBuilderExtensions.AddTUnit(builder.Logging, TestContext.Current!);
// Or via IServiceCollection
-services.AddTUnitLogging(TestContext.Current!);
+TUnit.Logging.Microsoft.ServiceCollectionExtensions.AddTUnitLogging(services, TestContext.Current!);
```
All log output is routed through TUnit's console interceptor and sink pipeline, so logs appear in test output, IDE test explorers, and the console (when using `--output Detailed`).
@@ -621,28 +622,38 @@ If a resource is shared (database, queue, cache), each test must use its own iso
:::
```csharp
-// ❌ BAD: All tests share the same table - will cause flaky failures
-protected override void ConfigureTestConfiguration(IConfigurationBuilder config)
+public class SharedTableTests : TestsBase
{
- config.AddInMemoryCollection(new Dictionary
+ // ❌ BAD: All tests share the same table - will cause flaky failures
+ protected override void ConfigureTestConfiguration(IConfigurationBuilder config)
{
- { "Database:TableName", "todos" } // Shared = flaky!
- });
+ config.AddInMemoryCollection(new Dictionary
+ {
+ { "Database:TableName", "todos" } // Shared = flaky!
+ });
+ }
}
-// ✅ GOOD: Each test gets its own table
-protected override async Task SetupAsync()
+public class IsolatedTableTests : TestsBase
{
- TableName = GetIsolatedName("todos"); // "Test_42_todos"
- await CreateTableAsync(TableName);
-}
+ private string TableName { get; set; } = null!;
-protected override void ConfigureTestConfiguration(IConfigurationBuilder config)
-{
- config.AddInMemoryCollection(new Dictionary
+ // ✅ GOOD: Each test gets its own table
+ protected override async Task SetupAsync()
{
- { "Database:TableName", TableName } // Isolated = reliable!
- });
+ TableName = GetIsolatedName("todos"); // "Test_42_todos"
+ await CreateTableAsync(TableName);
+ }
+
+ protected override void ConfigureTestConfiguration(IConfigurationBuilder config)
+ {
+ config.AddInMemoryCollection(new Dictionary
+ {
+ { "Database:TableName", TableName } // Isolated = reliable!
+ });
+ }
+
+ private static Task CreateTableAsync(string tableName) => Task.CompletedTask;
}
```
@@ -691,11 +702,19 @@ public async Task Cleanup()
### 4. Inject Containers at Factory Level
```csharp
+public sealed class PostgresContainerFixture : IAsyncInitializer, IAsyncDisposable
+{
+ public PostgreSqlContainer Container { get; } = new PostgreSqlBuilder("postgres:18").Build();
+
+ public Task InitializeAsync() => Container.StartAsync();
+ public ValueTask DisposeAsync() => Container.DisposeAsync();
+}
+
public class WebApplicationFactory : TestWebApplicationFactory
{
// Shared across all tests
- [ClassDataSource(Shared = SharedType.PerTestSession)]
- public PostgresContainer Postgres { get; init; } = null!;
+ [ClassDataSource(Shared = SharedType.PerTestSession)]
+ public PostgresContainerFixture Postgres { get; init; } = null!;
[ClassDataSource(Shared = SharedType.PerTestSession)]
public RedisContainer Redis { get; init; } = null!;
@@ -708,7 +727,7 @@ public class WebApplicationFactory : TestWebApplicationFactory
// Container wrapper
public class InMemoryPostgres : IAsyncInitializer, IAsyncDisposable
{
- public PostgreSqlContainer Container { get; } = new PostgreSqlBuilder().Build();
+ public PostgreSqlContainer Container { get; } = new PostgreSqlBuilder("postgres:18").Build();
public async Task InitializeAsync() => await Container.StartAsync();
public async ValueTask DisposeAsync() => await Container.DisposeAsync();
}
@@ -843,25 +862,32 @@ The key benefits:
3. You're not accidentally reading from a different source (e.g., `appsettings.json`)
```csharp
-// Factory sets default
-protected override void ConfigureWebHost(IWebHostBuilder builder)
+public class DefaultConfigurationFactory : TestWebApplicationFactory
{
- builder.ConfigureAppConfiguration((_, config) =>
+ // Factory sets default
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
{
- config.AddInMemoryCollection(new Dictionary
+ builder.ConfigureAppConfiguration((_, config) =>
{
- { "Database:ConnectionString", "factory-default" }
+ config.AddInMemoryCollection(new Dictionary
+ {
+ { "Database:ConnectionString", "factory-default" }
+ });
});
- });
+ }
}
-// Test overrides - this WILL work because it runs after
-protected override void ConfigureTestConfiguration(IConfigurationBuilder config)
+public class OverrideConfigurationTests
+ : WebApplicationTest
{
- config.AddInMemoryCollection(new Dictionary
+ // Test overrides - this WILL work because it runs after
+ protected override void ConfigureTestConfiguration(IConfigurationBuilder config)
{
- { "Database:ConnectionString", "test-specific-value" } // This wins!
- });
+ config.AddInMemoryCollection(new Dictionary
+ {
+ { "Database:ConnectionString", "test-specific-value" } // This wins!
+ });
+ }
}
```
@@ -882,10 +908,11 @@ protected override void ConfigureTestConfiguration(IConfigurationBuilder config)
```csharp
// BAD: All parallel tests share the same table
-var tableName = "todos";
+var sharedTableName = "todos";
// GOOD: Each test gets its own table
-var tableName = GetIsolatedName("todos"); // "Test_42_todos", "Test_43_todos", etc.
+var isolatedTableName = GetIsolatedName("todos"); // "Test_42_todos", "Test_43_todos", etc.
+TestContext.Current!.Output.WriteLine($"{sharedTableName} -> {isolatedTableName}");
```
### Can I have different factory configurations for different test classes?
@@ -1027,28 +1054,36 @@ public class MyTest : TestsBase
**Problem:** You set configuration values in `ConfigureWebHost` using `ConfigureAppConfiguration`, but your app's `Program.cs` doesn't see them during startup. Your breakpoint in Program.cs hits **before** the `ConfigureAppConfiguration` callback.
-
```csharp
-// Factory - this approach has a timing issue!
-protected override void ConfigureWebHost(IWebHostBuilder builder)
+public class DeferredConfigurationFactory : TestWebApplicationFactory
{
- Console.WriteLine("ConfigureWebHost called"); // This runs first...
-
- builder.ConfigureAppConfiguration((_, config) =>
+ // Factory - this approach has a timing issue!
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
{
- Console.WriteLine("ConfigureAppConfiguration callback"); // ...but THIS runs AFTER Program.cs!
- config.AddInMemoryCollection(new Dictionary
+ Console.WriteLine("ConfigureWebHost called"); // This runs first...
+
+ builder.ConfigureAppConfiguration((_, config) =>
{
- { "SomeKey", "SomeValue" }
+ Console.WriteLine("ConfigureAppConfiguration callback"); // ...but THIS runs AFTER Program.cs!
+ config.AddInMemoryCollection(new Dictionary
+ {
+ { "SomeKey", "SomeValue" }
+ });
});
- });
+ }
}
-// Program.cs - this runs BEFORE ConfigureAppConfiguration callback!
-var builder = WebApplication.CreateBuilder(args);
-if (builder.Configuration["SomeKey"] != "SomeValue")
+public static class ApplicationStartup
{
- throw new InvalidOperationException("SomeKey not found!"); // This throws!
+ public static void Configure(string[] args)
+ {
+ // Program.cs - this runs BEFORE ConfigureAppConfiguration callback!
+ var builder = Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(args);
+ if (builder.Configuration["SomeKey"] != "SomeValue")
+ {
+ throw new InvalidOperationException("SomeKey not found!"); // This throws!
+ }
+ }
}
```
diff --git a/docs/docs/examples/complex-test-infrastructure.md b/docs/docs/examples/complex-test-infrastructure.md
index 1f1d460355c..05c87fd47a8 100644
--- a/docs/docs/examples/complex-test-infrastructure.md
+++ b/docs/docs/examples/complex-test-infrastructure.md
@@ -1,4 +1,5 @@
-
+
+
# Complex Test Infrastructure Orchestration
@@ -37,7 +38,7 @@ public class InMemoryKafka : IAsyncInitializer, IAsyncDisposable
[ClassDataSource(Shared = SharedType.PerTestSession)]
public required DockerNetwork DockerNetwork { get; init; }
- public KafkaContainer Container => field ??= new KafkaBuilder()
+ public KafkaContainer Container => field ??= new KafkaBuilder("confluentinc/cp-kafka:8.2.0")
.WithNetwork(DockerNetwork.Instance) // Uses the injected network
.Build();
@@ -59,7 +60,7 @@ public class KafkaUI : IAsyncInitializer, IAsyncDisposable
[ClassDataSource(Shared = SharedType.PerTestSession)]
public required InMemoryKafka Kafka { get; init; }
- public IContainer Container => field ??= new ContainerBuilder()
+ public IContainer Container => field ??= new ContainerBuilder("confluentinc/cp-enterprise-control-center:8.2.0")
.WithNetwork(DockerNetwork.Instance)
.WithImage("provectuslabs/kafka-ui:latest")
.WithPortBinding(8080, 8080)
@@ -116,6 +117,13 @@ public class WebApplicationFactory : WebApplicationFactory, IAsyncIniti
});
}
}
+
+public class InMemoryRedis : IAsyncInitializer, IAsyncDisposable
+{
+ public RedisContainer Container { get; } = new RedisBuilder("redis:8.2").Build();
+ public Task InitializeAsync() => Container.StartAsync();
+ public ValueTask DisposeAsync() => Container.DisposeAsync();
+}
```
## Writing Clean Tests
@@ -123,7 +131,7 @@ public class WebApplicationFactory : WebApplicationFactory, IAsyncIniti
Your actual test code remains clean and focused:
```csharp
-public class Tests : TestsBase
+public class Tests
{
[ClassDataSource(Shared = SharedType.PerTestSession)]
public required WebApplicationFactory WebApplicationFactory { get; init; }
@@ -181,7 +189,7 @@ public class InMemoryPostgreSqlDatabase : IAsyncInitializer, IAsyncDisposable
public required DockerNetwork DockerNetwork { get; init; }
- public PostgreSqlContainer Container => field ??= new PostgreSqlBuilder()
+ public PostgreSqlContainer Container => field ??= new PostgreSqlBuilder("postgres:18")
.WithUsername("User")
.WithPassword("Password")
.WithDatabase("TestDatabase")
@@ -212,24 +220,26 @@ See the full pattern with `IModelCacheKeyFactory`, `EnsureCreatedAsync()`, and s
### Without TUnit (Traditional Approach)
```csharp
+using Xunit;
+
public class TestFixture : IAsyncLifetime
{
private INetwork? _network;
private KafkaContainer? _kafka;
private IContainer? _kafkaUi;
- public async Task InitializeAsync()
+ public async ValueTask InitializeAsync()
{
// Manual orchestration required
_network = new NetworkBuilder().Build();
await _network.CreateAsync();
- _kafka = new KafkaBuilder()
+ _kafka = new KafkaBuilder("confluentinc/cp-kafka:8.2.0")
.WithNetwork(_network)
.Build();
await _kafka.StartAsync();
- _kafkaUi = new ContainerBuilder()
+ _kafkaUi = new ContainerBuilder("provectuslabs/kafka-ui:latest")
.WithNetwork(_network)
.WithEnvironment("KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS",
$"{_kafka.Name}:9093") // Manual wiring
@@ -237,7 +247,7 @@ public class TestFixture : IAsyncLifetime
await _kafkaUi.StartAsync();
}
- public async Task DisposeAsync()
+ public async ValueTask DisposeAsync()
{
// Manual cleanup in reverse order
if (_kafkaUi != null) await _kafkaUi.DisposeAsync();
diff --git a/docs/docs/examples/fscheck.md b/docs/docs/examples/fscheck.md
index ba76a472251..6d6ed1e0813 100644
--- a/docs/docs/examples/fscheck.md
+++ b/docs/docs/examples/fscheck.md
@@ -178,7 +178,7 @@ using FsCheck.Fluent;
public class Person
{
- public string Name { get; set; }
+ public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
diff --git a/docs/docs/examples/instrumenting-global-test-ids.md b/docs/docs/examples/instrumenting-global-test-ids.md
index 58662a9cdc7..7cb1d6e7445 100644
--- a/docs/docs/examples/instrumenting-global-test-ids.md
+++ b/docs/docs/examples/instrumenting-global-test-ids.md
@@ -1,4 +1,3 @@
-
# Instrumenting: Global Test IDs
@@ -44,7 +43,6 @@ static class TestContextExtensions
Assign unique identifiers to all tests in the assembly by decorating in `AssemblyInfo.cs`:
-
```csharp
[assembly: AssignTestIdentifiers]
```
@@ -90,3 +88,4 @@ class MyTestClassThatNeedsUniqueTestIds
The test identifier for each test is assigned in the order that TUnit discovers the tests. The test identifier is unique for each test and is guaranteed to be assigned before the test starts. For other uses cases, you would need to adjust the implementation of `AssignTestIdentifiersAttribute` to suit your needs. For example, you could choose to use GUIDs instead of integers. We've only used integers to match the Redis database number example.
+
diff --git a/docs/docs/examples/opentelemetry.md b/docs/docs/examples/opentelemetry.md
index 6c9113cd44e..fe569269720 100644
--- a/docs/docs/examples/opentelemetry.md
+++ b/docs/docs/examples/opentelemetry.md
@@ -1,4 +1,3 @@
-
# OpenTelemetry Tracing
@@ -240,21 +239,15 @@ Swap the exporter in the setup code above. Each exporter needs its own NuGet pac
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
```
-
```csharp
-.AddOtlpExporter(opts => opts.Endpoint = new Uri("http://localhost:4317"))
+using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddOtlpExporter(opts => opts.Endpoint = new Uri("http://localhost:4317"))
+ .Build();
```
### Zipkin
-```bash
-dotnet add package OpenTelemetry.Exporter.Zipkin
-```
-
-
-```csharp
-.AddZipkinExporter(opts => opts.Endpoint = new Uri("http://localhost:9411/api/v2/spans"))
-```
+The Zipkin exporter is obsolete. Export with OTLP to an OpenTelemetry Collector configured with a Zipkin exporter instead.
### ASP.NET Core Integration Tests
@@ -326,11 +319,6 @@ For manual setups, add this processor to your tracer builder:
using System.Diagnostics;
using OpenTelemetry;
-// Usage:
-using var tracerProvider = Sdk.CreateTracerProviderBuilder()
- .AddProcessor(new TUnitTagProcessor())
- .Build();
-
public sealed class TUnitTagProcessor : BaseProcessor
{
public override void OnStart(Activity activity)
@@ -342,6 +330,11 @@ public sealed class TUnitTagProcessor : BaseProcessor
}
}
}
+
+// Usage:
+using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddProcessor(new TUnitTagProcessor())
+ .Build();
```
Register the correlation processor **before** any synchronous exporter (`SimpleExportProcessor`-based). The built-in `TUnitTestCorrelationProcessor` tags at both `OnStart` and `OnEnd`, and a `SimpleExport`-wrapped exporter that runs first would serialize the activity before the tag is applied. `BatchExportProcessor` (the default for OTLP/Jaeger/Zipkin) defers serialization, so order doesn't matter there.
diff --git a/docs/docs/execution/cancellation.md b/docs/docs/execution/cancellation.md
index ee8837dfc2c..faf1a9161ed 100644
--- a/docs/docs/execution/cancellation.md
+++ b/docs/docs/execution/cancellation.md
@@ -1,10 +1,8 @@
-
# Cancelling a Test
Call `TestContext.Execution.Cancel()` to request cooperative cancellation of the current test without affecting other tests or the test session:
-
```csharp
[Test]
public async Task ProcessMessages(CancellationToken cancellationToken)
diff --git a/docs/docs/execution/engine-modes.md b/docs/docs/execution/engine-modes.md
index 1bd514da83e..c62adca5b41 100644
--- a/docs/docs/execution/engine-modes.md
+++ b/docs/docs/execution/engine-modes.md
@@ -1,4 +1,3 @@
-
# Engine Modes
@@ -56,19 +55,21 @@ This is the recommended approach when you need reflection mode for a specific te
**Example: bUnit Test Project**
```csharp
+using Bunit;
+
// Add this to enable reflection mode for your bUnit tests
[assembly: ReflectionMode]
namespace MyApp.Tests;
-public class CounterComponentTests : TestContext
+public class CounterComponentTests : BunitContext
{
[Test]
- public void CounterStartsAtZero()
+ public async Task CounterStartsAtZero()
{
// Test Razor components that are source-generated at compile time
- var cut = RenderComponent();
- cut.Find("p").TextContent.ShouldBe("Current count: 0");
+ var cut = Render();
+ await Assert.That(cut.Find("p").TextContent).IsEqualTo("Current count: 0");
}
}
```
@@ -115,7 +116,7 @@ Add this MSBuild property to your test project file (`.csproj`):
-
+
@@ -123,18 +124,20 @@ Add this MSBuild property to your test project file (`.csproj`):
Then in your code:
```csharp
+using Bunit;
+
// Enable reflection mode for Razor component testing
[assembly: ReflectionMode]
namespace MyApp.Tests;
-public class CounterComponentTests : TestContext
+public class CounterComponentTests : BunitContext
{
[Test]
- public void CounterStartsAtZero()
+ public async Task CounterStartsAtZero()
{
- var cut = RenderComponent();
- cut.Find("p").TextContent.ShouldBe("Current count: 0");
+ var cut = Render();
+ await Assert.That(cut.Find("p").TextContent).IsEqualTo("Current count: 0");
}
}
```
diff --git a/docs/docs/execution/parallelism.md b/docs/docs/execution/parallelism.md
index fe68d80b160..f52a7f0f17e 100644
--- a/docs/docs/execution/parallelism.md
+++ b/docs/docs/execution/parallelism.md
@@ -2,7 +2,6 @@
sidebar_position: 10
---
-
# Controlling Parallelism
@@ -106,7 +105,7 @@ public class OrderRepositoryTests
public async Task Create_Order()
{
var order = await OrderRepository.CreateAsync("item-1");
- await Assert.That(order.Id).IsNotNull();
+ await Assert.That(order.Id).IsNotEmptyGuid();
}
}
@@ -163,9 +162,13 @@ With a limit of `2`, at most two of these 20 test invocations execute at the sam
### Assembly-Level Limiter
-
```csharp
-[assembly: ParallelLimiter]
+[assembly: ParallelLimiter]
+
+public record MyAssemblyParallelLimit : IParallelLimit
+{
+ public int Limit => 2;
+}
```
More specific attributes override less specific ones. Precedence: Method > Class > Assembly.
diff --git a/docs/docs/execution/parameters.md b/docs/docs/execution/parameters.md
index 539240d3d84..722d2429bcd 100644
--- a/docs/docs/execution/parameters.md
+++ b/docs/docs/execution/parameters.md
@@ -1,4 +1,3 @@
-
# Test Parameters
@@ -51,7 +50,6 @@ public class MyTests
### Environment-specific configuration
-
```csharp
[Before(Test)]
public void SetupEnvironment()
@@ -65,14 +63,13 @@ public void SetupEnvironment()
### Conditional test logic
-
```csharp
[Test]
public async Task IntegrationTest()
{
if (!TestContext.Parameters.ContainsKey("run-integration"))
{
- Assert.Skip("Integration tests require --test-parameter run-integration=true");
+ Skip.Test("Integration tests require --test-parameter run-integration=true");
}
// Run the integration test...
@@ -81,17 +78,17 @@ public async Task IntegrationTest()
### Passing secrets or connection strings
-
```csharp
[Test]
public async Task DatabaseTest()
{
if (!TestContext.Parameters.TryGetValue("connection-string", out var connectionStrings))
{
- Assert.Skip("Requires --test-parameter connection-string=...");
+ Skip.Test("Requires --test-parameter connection-string=...");
}
- using var connection = new SqlConnection(connectionStrings.First());
+ await using var connection = new NpgsqlConnection(
+ connectionStrings.FirstOrDefault() ?? throw new InvalidOperationException("Missing connection string"));
// ...
}
```
diff --git a/docs/docs/execution/timeouts.md b/docs/docs/execution/timeouts.md
index abb0f7e579d..096ac7a031e 100644
--- a/docs/docs/execution/timeouts.md
+++ b/docs/docs/execution/timeouts.md
@@ -2,7 +2,6 @@
sidebar_position: 5
---
-
# Timeouts
@@ -42,7 +41,6 @@ If the HTTP call takes longer than 30 seconds, `cancellationToken` is cancelled,
When a test has both `[Timeout]` and `[Retry]`, each retry attempt gets its own fresh timeout. If the first attempt times out at 5 seconds, the retry starts from zero with a new 5-second window:
-
```csharp
[Test]
[Timeout(5_000)]
diff --git a/docs/docs/extending/argument-formatters.md b/docs/docs/extending/argument-formatters.md
index b04eb440c83..17364906da7 100644
--- a/docs/docs/extending/argument-formatters.md
+++ b/docs/docs/extending/argument-formatters.md
@@ -1,4 +1,3 @@
-
# Argument Formatters
@@ -15,8 +14,10 @@ For example:
[ArgumentDisplayFormatter]
public async Task Test(SomeClass someClass)
{
- await Assert.That(TestContext.Current!.GetDisplayName()).IsEqualTo("A super important test!");
+ await Assert.That(TestContext.Current!.Metadata.DisplayName).IsEqualTo("A super important test!");
}
+
+ public static IEnumerable SomeMethod() => [new SomeClass()];
```
```csharp
@@ -29,7 +30,11 @@ public class MyFormatter : ArgumentDisplayFormatter
public override string FormatValue(object? value)
{
- var someClass = (SomeClass)value;
+ if (value is not SomeClass someClass)
+ {
+ throw new ArgumentException("Value must be a SomeClass instance.", nameof(value));
+ }
+
return $"One: {someClass.One} | Two: {someClass.Two}";
}
}
@@ -39,3 +44,4 @@ public class MyFormatter : ArgumentDisplayFormatter
You can apply multiple `[ArgumentDisplayFormatter]` attributes if you have different types to format.
The first formatter whose `CanHandle` returns true will be used.
:::
+
diff --git a/docs/docs/extending/data-source-generators.md b/docs/docs/extending/data-source-generators.md
index 06cb4b1eb62..fcf56291309 100644
--- a/docs/docs/extending/data-source-generators.md
+++ b/docs/docs/extending/data-source-generators.md
@@ -1,4 +1,3 @@
-
# Data Source Generators
@@ -36,7 +35,7 @@ public class MyTestClass(SomeClass1 someClass1, SomeClass2 someClass2, SomeClass
[AutoFixtureGenerator]
public async Task Test(int value, string value2, bool value3)
{
- // ...
+ _ = (someClass1, someClass2, someClass3, value, value2, value3);
}
}
@@ -74,15 +73,11 @@ public class DatabaseDataGeneratorAttribute : AsyncDataSourceGeneratorAttribu
protected override async IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata)
{
- await using var connection = new SqlConnection(_connectionString);
+ await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync();
-
- var entities = await connection.QueryAsync("SELECT * FROM " + typeof(T).Name);
-
- foreach (var entity in entities)
- {
- yield return () => Task.FromResult(entity);
- }
+
+ var fixture = new Fixture();
+ yield return () => Task.FromResult(fixture.Create());
}
}
@@ -146,7 +141,7 @@ public class RepositoryTests(DatabaseContext context)
[Test]
public async Task TestRepository()
{
- // context is populated by AutoFixture
+ _ = context;
}
}
```
@@ -166,18 +161,21 @@ After each `yield`, the execution is passed back to TUnit, and TUnit will set a
The `TestBuilderContext` object exposes `Events` - And you can register a delegate to be invoked on them at the point in the test lifecycle that you wish.
```csharp
-public override IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata)
+public sealed class ContextAwareDataGeneratorAttribute : DataSourceGeneratorAttribute
{
- dataGeneratorMetadata.TestBuilderContext.Current; // <-- Initial Context for first test
-
- yield return () => 1;
-
- dataGeneratorMetadata.TestBuilderContext.Current; // <-- This is now a different context object, as we yielded
- dataGeneratorMetadata.TestBuilderContext.Current; // <-- This is still the same as above because it'll only change on a yield
-
- yield return () => 2;
-
- dataGeneratorMetadata.TestBuilderContext.Current; // <-- A new object again
+ protected override IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata)
+ {
+ _ = dataGeneratorMetadata.TestBuilderContext.Current; // Initial context for first test
+
+ yield return () => 1;
+
+ _ = dataGeneratorMetadata.TestBuilderContext.Current; // A different context after yielding
+ _ = dataGeneratorMetadata.TestBuilderContext.Current; // Still the same until the next yield
+
+ yield return () => 2;
+
+ _ = dataGeneratorMetadata.TestBuilderContext.Current; // A new context again
+ }
}
```
diff --git a/docs/docs/extending/exception-handling.md b/docs/docs/extending/exception-handling.md
index 7e2ac14f89b..5173ba75362 100644
--- a/docs/docs/extending/exception-handling.md
+++ b/docs/docs/extending/exception-handling.md
@@ -1,4 +1,3 @@
-
# Exception Handling
@@ -8,7 +7,6 @@ When a test fails, TUnit throws an exception. Most of the time you don't need to
If a test can't run because of some runtime condition, throw `SkipTestException`. The test will be reported as skipped rather than failed.
-
```csharp
[Test]
public async Task RequiresExternalService()
@@ -26,7 +24,6 @@ public async Task RequiresExternalService()
If a test can't determine a pass/fail result, throw `InconclusiveTestException`.
-
```csharp
[Test]
public async Task CheckFeatureFlag()
@@ -46,7 +43,6 @@ public async Task CheckFeatureFlag()
In an `[After(Test)]` hook, you can check whether the test failed via `TestContext`:
-
```csharp
[After(Test)]
public async Task TakeScreenshotOnFailure(TestContext context)
diff --git a/docs/docs/extending/extension-points.md b/docs/docs/extending/extension-points.md
index d21d68f1385..c93c66e3c7d 100644
--- a/docs/docs/extending/extension-points.md
+++ b/docs/docs/extending/extension-points.md
@@ -1,4 +1,3 @@
-
# Extension Points
@@ -37,7 +36,7 @@ public class TimingTestExecutor : ITestExecutor
finally
{
stopwatch.Stop();
- context.WriteLine($"Test execution took: {stopwatch.ElapsedMilliseconds}ms");
+ context.Output.WriteLine($"Test execution took: {stopwatch.ElapsedMilliseconds}ms");
// You could also send this to telemetry
TelemetryClient.TrackMetric("TestDuration", stopwatch.ElapsedMilliseconds);
@@ -52,13 +51,15 @@ To use your custom test executor, apply the `TestExecutorAttribute` at the assem
```csharp
// Assembly-level (applies to all tests in the assembly)
-[assembly: TestExecutor]
+[assembly: TestExecutor]
-// Or use the non-generic version
-[assembly: TestExecutor(typeof(TimingTestExecutor))]
+public sealed class RegistrationTimingTestExecutor : ITestExecutor
+{
+ public ValueTask ExecuteTest(TestContext context, Func action) => action();
+}
// Class-level (applies to all tests in the class)
-[TestExecutor]
+[TestExecutor]
public class MyTestClass
{
[Test]
@@ -68,12 +69,15 @@ public class MyTestClass
}
}
-// Method-level (applies to specific test)
-[Test]
-[TestExecutor]
-public async Task MyTest()
+public class MethodLevelExecutorTests
{
- // Test logic here
+ // Method-level (applies to specific test)
+ [Test]
+ [TestExecutor]
+ public async Task MyTest()
+ {
+ // Test logic here
+ }
}
```
@@ -232,10 +236,15 @@ You can also apply it at the class or assembly level to affect all hooks in that
```csharp
// Assembly-level (applies to all hooks in the assembly)
-[assembly: HookExecutor]
+[assembly: HookExecutor]
+
+public sealed class AssemblyLoggingHookExecutor : GenericAbstractExecutor
+{
+ protected override ValueTask ExecuteAsync(Func action) => action();
+}
// Class-level (applies to all hooks in the class)
-[HookExecutor]
+[HookExecutor]
public class MyTestClass
{
[Before(Test)]
@@ -258,9 +267,8 @@ public class DispatchAttribute : Attribute, ITestRegisteredEventReceiver
public ValueTask OnTestRegistered(TestRegisteredContext context)
{
- var executor = new MyCustomExecutor();
- context.SetTestExecutor(executor);
- context.SetHookExecutor(executor);
+ context.SetTestExecutor(new TimingTestExecutor());
+ context.SetHookExecutor(new LoggingHookExecutor());
return default;
}
}
@@ -355,7 +363,7 @@ public class TestReporterAttribute : Attribute, ITestStartEventReceiver, ITestEn
public async ValueTask OnTestStart(TestContext context)
{
await ReportingService.ReportTestStarted(
- context.GetDisplayName(),
+ context.Metadata.DisplayName,
context.Metadata.TestDetails.ClassType.FullName,
context.Metadata.TestDetails.TestMethodArguments
);
@@ -364,7 +372,7 @@ public class TestReporterAttribute : Attribute, ITestStartEventReceiver, ITestEn
public async ValueTask OnTestEnd(TestContext context)
{
await ReportingService.ReportTestCompleted(
- context.GetDisplayName(),
+ context.Metadata.DisplayName,
context.Execution.Result?.State,
context.Execution.Result?.Duration,
context.Execution.Result?.Exception?.Message
@@ -378,6 +386,9 @@ public class TestReporterAttribute : Attribute, ITestStartEventReceiver, ITestEn
Event receivers are registered by implementing the interfaces in an attribute class, then applying that attribute at the assembly, class, or method level:
```csharp
+// Apply at assembly level
+[assembly: CustomEventReceiver]
+
// Create an attribute that implements the event receiver interfaces
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
public class CustomEventReceiverAttribute : Attribute, ITestStartEventReceiver, ITestEndEventReceiver
@@ -386,20 +397,17 @@ public class CustomEventReceiverAttribute : Attribute, ITestStartEventReceiver,
public ValueTask OnTestStart(TestContext context)
{
- Console.WriteLine($"Test starting: {context.GetDisplayName()}");
+ Console.WriteLine($"Test starting: {context.Metadata.DisplayName}");
return default;
}
public ValueTask OnTestEnd(TestContext context)
{
- Console.WriteLine($"Test ended: {context.GetDisplayName()} - {context.Execution.Result?.State}");
+ Console.WriteLine($"Test ended: {context.Metadata.DisplayName} - {context.Execution.Result?.State}");
return default;
}
}
-// Apply at assembly level
-[assembly: CustomEventReceiver]
-
// Or at class level
[CustomEventReceiver]
public class MyTestClass
@@ -408,10 +416,13 @@ public class MyTestClass
public async Task MyTest() { }
}
-// Or at method level
-[Test]
-[CustomEventReceiver]
-public async Task MyTest() { }
+public class MethodEventReceiverTests
+{
+ // Or at method level
+ [Test]
+ [CustomEventReceiver]
+ public Task MyTest() => Task.CompletedTask;
+}
```
## Parallel Execution Control
@@ -516,7 +527,7 @@ Example:
```csharp
public class DatabaseTests : IAsyncInitializer
{
- private DatabaseConnection _connection;
+ private DatabaseConnection _connection = null!;
public async Task InitializeAsync()
{
@@ -565,7 +576,7 @@ public class TestCaseFixture : IAsyncDiscoveryInitializer, IAsyncDisposable
public async Task InitializeAsync()
{
// This runs during DISCOVERY, not just execution
- _testCases = await LoadTestCasesFromDatabaseAsync();
+ _testCases = [.. await LoadTestCasesFromDatabaseAsync()];
}
public IEnumerable GetTestCases() => _testCases;
@@ -639,7 +650,7 @@ public class TransactionalTestExecutor : ITestExecutor
public async ValueTask ExecuteTest(TestContext context, Func action)
{
// Get the database connection from DI
- var dbContext = context.GetService();
+ var dbContext = new ApplicationDbContext();
using var transaction = await dbContext.Database.BeginTransactionAsync();
diff --git a/docs/docs/extending/libraries.md b/docs/docs/extending/libraries.md
index 3ca9d24d866..81e44a3858b 100644
--- a/docs/docs/extending/libraries.md
+++ b/docs/docs/extending/libraries.md
@@ -1,4 +1,3 @@
-
# Libraries
@@ -85,7 +84,7 @@ public class OrderTests : DatabaseTestBase
{
var order = await OrderService.CreateAsync("item-1");
- await Assert.That(order.Id).IsNotNull();
+ await Assert.That(order.Id).IsNotEqualTo(Guid.Empty);
}
}
```
diff --git a/docs/docs/extending/logging.md b/docs/docs/extending/logging.md
index c71136377a8..d1222a268e5 100644
--- a/docs/docs/extending/logging.md
+++ b/docs/docs/extending/logging.md
@@ -1,4 +1,3 @@
-
# Logging
@@ -9,6 +8,8 @@ TUnit provides a flexible logging system that captures all test output and route
By default, TUnit intercepts any logs to `Console.WriteLine()` and correlates them to the test that triggered the log using the current async context.
```csharp
+using TUnit.Core.Logging;
+
[Test]
public async Task MyTest()
{
@@ -21,6 +22,8 @@ public async Task MyTest()
For more control, use `TestContext.Current.GetDefaultLogger()` to get a logger instance:
```csharp
+using TUnit.Core.Logging;
+
[Test]
public async Task MyTest()
{
@@ -229,6 +232,8 @@ Implement the `ILogSink` interface to create a custom sink:
```csharp
using TUnit.Core;
using TUnit.Core.Logging;
+using Serilog;
+using LogLevel = TUnit.Core.Logging.LogLevel;
public class FileLogSink : ILogSink, IAsyncDisposable
{
@@ -285,10 +290,10 @@ public class TestSetup
public static void SetupLogging()
{
// Register by instance (for sinks needing configuration)
- TUnitLoggerFactory.AddSink(new FileLogSink("test-output.log"));
+ TUnit.Core.Logging.TUnitLoggerFactory.AddSink(new FileLogSink("test-output.log"));
// Or register by type (for simple sinks)
- TUnitLoggerFactory.AddSink();
+ TUnit.Core.Logging.TUnitLoggerFactory.AddSink();
}
}
```
@@ -300,6 +305,8 @@ Sinks that implement `IDisposable` or `IAsyncDisposable` are automatically dispo
The `context` parameter provides information about where the log originated:
```csharp
+using LogLevel = TUnit.Core.Logging.LogLevel;
+
public void Log(LogLevel level, string message, Exception? exception, Context? context)
{
switch (context)
@@ -332,13 +339,17 @@ public void Log(LogLevel level, string message, Exception? exception, Context? c
Here's an example sink that sends logs to Seq:
```csharp
+using TUnit.Core.Logging;
+using Serilog;
+using LogLevel = TUnit.Core.Logging.LogLevel;
+
public class SeqLogSink : ILogSink, IDisposable
{
private readonly Serilog.ILogger _logger;
public SeqLogSink(string seqUrl)
{
- _logger = new LoggerConfiguration()
+ _logger = new Serilog.LoggerConfiguration()
.WriteTo.Seq(seqUrl)
.CreateLogger();
}
@@ -408,7 +419,7 @@ using TUnit.Logging.Microsoft;
var host = Host.CreateDefaultBuilder()
.ConfigureLogging(logging =>
{
- logging.AddTUnit(TestContext.Current!);
+ TUnit.Logging.Microsoft.LoggingBuilderExtensions.AddTUnit(logging, TestContext.Current!);
})
.Build();
```
@@ -416,7 +427,7 @@ var host = Host.CreateDefaultBuilder()
Or via `IServiceCollection`:
```csharp
-services.AddTUnitLogging(TestContext.Current!);
+TUnit.Logging.Microsoft.ServiceCollectionExtensions.AddTUnitLogging(services, TestContext.Current!);
```
All `ILogger` output is routed through TUnit's console interceptor and sink pipeline, appearing in test output, IDE test explorers, and the console.
@@ -442,6 +453,9 @@ Available levels (from least to most severe):
You can also create custom loggers by inheriting from `DefaultLogger`:
```csharp
+using TUnit.Core.Logging;
+using LogLevel = TUnit.Core.Logging.LogLevel;
+
public class TestHeaderLogger : DefaultLogger
{
private bool _hasOutputHeader;
diff --git a/docs/docs/getting-started/writing-your-first-test.md b/docs/docs/getting-started/writing-your-first-test.md
index 528e6364c1a..408662b77fb 100644
--- a/docs/docs/getting-started/writing-your-first-test.md
+++ b/docs/docs/getting-started/writing-your-first-test.md
@@ -1,4 +1,3 @@
-
# Writing your first test
@@ -81,7 +80,6 @@ Tests will pass if they execute successfully without any exceptions.
Test methods can be either synchronous or asynchronous:
-
```csharp
[Test]
public void SynchronousTest() // ✅ Valid - synchronous test
diff --git a/docs/docs/guides/distributed-tracing.md b/docs/docs/guides/distributed-tracing.md
index 40b949ce2e4..8be3e56b3fd 100644
--- a/docs/docs/guides/distributed-tracing.md
+++ b/docs/docs/guides/distributed-tracing.md
@@ -2,7 +2,6 @@
sidebar_position: 20
---
-
# Distributed Tracing
@@ -55,14 +54,15 @@ Install [`TUnit.OpenTelemetry`](/docs/examples/opentelemetry#option-a-zero-confi
Point the OTLP exporter at Seq's ingestion endpoint:
-
```csharp
-.AddOtlpExporter(opts =>
-{
- opts.Endpoint = new Uri("http://localhost:5341/ingest/otlp/v1/traces");
- opts.Protocol = OtlpExportProtocol.HttpProtobuf;
- opts.Headers = "X-Seq-ApiKey=your-key";
-})
+using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddOtlpExporter(opts =>
+ {
+ opts.Endpoint = new Uri("http://localhost:5341/ingest/otlp/v1/traces");
+ opts.Protocol = OtlpExportProtocol.HttpProtobuf;
+ opts.Headers = "X-Seq-ApiKey=your-key";
+ })
+ .Build();
```
Useful Seq queries:
@@ -76,9 +76,10 @@ test.case.result.status = 'fail' -- only failures
### Jaeger or Tempo
-
```csharp
-.AddOtlpExporter(opts => opts.Endpoint = new Uri("http://localhost:4317"))
+using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddOtlpExporter(opts => opts.Endpoint = new Uri("http://localhost:4317"))
+ .Build();
```
Jaeger groups by trace ID, so each test appears as a separate trace. Use the tag search box (`tunit.session.id=""`) to find all traces from one run.
@@ -113,7 +114,6 @@ TUnit handles this automatically: a module initializer in `TUnit.Core` replaces
For the SUT side, if it shares the test process (e.g. `TestWebApplicationFactory`), alignment flows automatically. For out-of-process SUTs that don't reference `TUnit.Core`, align the propagator yourself on startup — either match `DistributedContextPropagator.Current` or, if you use the OpenTelemetry SDK:
-
```csharp
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
@@ -148,7 +148,6 @@ Use [`TestWebApplicationFactory`](/docs/examples/aspnet) or wrap with `Traced
Opt out per-test when the SUT already instruments its own outbound HTTP (for example via the OpenTelemetry HttpClient instrumentation) by setting `WebApplicationTestOptions.AutoPropagateHttpClientFactory = false`:
-
```csharp
protected override void ConfigureTestOptions(WebApplicationTestOptions options)
{
@@ -172,22 +171,19 @@ Install [`TUnit.OpenTelemetry`](/docs/examples/opentelemetry#option-a-zero-confi
Read the endpoint from `AutoReceiver.Endpoint` and plumb it into the SUT:
-
```csharp
using TUnit.OpenTelemetry;
var endpoint = AutoReceiver.Endpoint; // e.g. "http://127.0.0.1:41234"
+using var process = new Process { StartInfo = new ProcessStartInfo("dotnet") };
process.StartInfo.EnvironmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint;
process.StartInfo.EnvironmentVariables["OTEL_EXPORTER_OTLP_PROTOCOL"] = "http/protobuf";
```
For the receiver to associate incoming spans with the right test, register the SUT's trace ID before it runs:
-
```csharp
-using TUnit.Engine.Reporters.Html;
-
-ActivityCollector.Current?.RegisterExternalTrace(Activity.Current!.TraceId.ToString());
+TestContext.Current!.RegisterTrace(Activity.Current!.TraceId);
```
Spans arriving on a trace ID that wasn't registered are dropped (protects the report from unrelated traffic on shared runners). Each registered trace is capped at 100 external spans.
diff --git a/docs/docs/guides/html-report.md b/docs/docs/guides/html-report.md
index 251b21bd3c9..16c02002948 100644
--- a/docs/docs/guides/html-report.md
+++ b/docs/docs/guides/html-report.md
@@ -2,7 +2,6 @@
sidebar_position: 10
---
-
# HTML Test Report
@@ -22,7 +21,7 @@ The OS and runtime version are included automatically so that matrix builds (mul
Open it in any modern browser. The report is fully self-contained (single HTML file) and works offline.
-A machine-readable JSON sidecar (`{AssemblyName}-{os}-{tfm}.tunit-report.json`) is written alongside the HTML report. It powers [report aggregation](/docs/guides/report-aggregation) — merging reports from multiple test projects into one — and can be disabled with `TUNIT_DISABLE_JSON_REPORT=true`.
+A machine-readable JSON sidecar (`{AssemblyName}-{os}-{tfm}.tunit-report.json`) is written alongside the HTML report. It powers [report aggregation](/docs/guides/report-aggregation) — merging reports from multiple test projects into one — and can be disabled with `TUNIT_DISABLE_JSON_REPORT=true` or `context.Settings.Reporting.JsonReportEnabled = false`.
Running many test projects and want **one combined report instead of one per project**? See [Aggregated Reports](/docs/guides/report-aggregation).
@@ -54,6 +53,8 @@ export TUNIT_DISABLE_HTML_REPORTER=true
Accepts: `true`, `1`, `yes` (case-insensitive).
+For version-controlled project configuration, set `context.Settings.Reporting.HtmlReportEnabled = false` in a `[Before(HookType.TestDiscovery)]` hook instead.
+
### Deprecated: `--report-html` Flag
The `--report-html` flag is deprecated since the report is now generated by default. Using it will show a deprecation warning but will not cause an error.
@@ -136,6 +137,8 @@ This is useful if you:
The report file and the `GITHUB_STEP_SUMMARY` are still generated.
+This can also be configured in code with `context.Settings.Reporting.ArtifactUploadEnabled = false`.
+
### Viewing the Report
After the workflow run completes:
@@ -158,7 +161,6 @@ TUnit's test body runs under a per-test `System.Diagnostics.Activity`. Because `
For example, an integration test using `WebApplicationFactory`:
-
```csharp
[Test]
public async Task GetUsers_ReturnsOk()
@@ -180,16 +182,15 @@ The HTML report groups each test under its class. Backends like Seq, Jaeger, and
If your test communicates with an external service that runs in a **separate process** (and therefore has a different trace context), you can manually link its trace to the test:
-
```csharp
[Test]
public async Task ProcessOrder_SendsNotification()
{
- // Start some external work that creates its own trace
- var externalActivity = MyExternalService.StartProcessing(orderId);
+ // Obtain this from the external service's trace response or diagnostics.
+ var externalTraceId = ActivityTraceId.CreateRandom();
// Link that trace to this test so it appears in the HTML report
- TestContext.Current!.RegisterTrace(externalActivity.Context.TraceId);
+ TestContext.Current!.RegisterTrace(externalTraceId);
// ... wait for processing, assert results
}
@@ -201,7 +202,6 @@ Linked traces appear as a separate **"Linked Trace"** section below the test's m
You can access the current test's `Activity` to parent external work explicitly:
-
```csharp
[Test]
public async Task MyTest()
@@ -238,7 +238,7 @@ The collector uses **smart sampling**: spans from known test traces are fully re
### Report Not Generated
-- Check that `TUNIT_DISABLE_HTML_REPORTER` is not set in your environment
+- Check that `TUNIT_DISABLE_HTML_REPORTER` is not set and `context.Settings.Reporting.HtmlReportEnabled` is not `false`
- Verify that the `TestResults/` directory is writable
- Check the console output for any warning messages about report generation failures
diff --git a/docs/docs/guides/performance.md b/docs/docs/guides/performance.md
index e1c7f9b8f15..fcdd1cbf253 100644
--- a/docs/docs/guides/performance.md
+++ b/docs/docs/guides/performance.md
@@ -1,4 +1,3 @@
-
# Performance Best Practices
@@ -36,7 +35,7 @@ Benefits:
```csharp
// ❌ Bad: Heavy computation during discovery
-public static IEnumerable GetTestUsers()
+public static IEnumerable GetTestUsersWithDatabaseQuery()
{
// This runs during test discovery!
var users = DatabaseQuery.GetAllUsers();
@@ -44,7 +43,7 @@ public static IEnumerable GetTestUsers()
}
// ✅ Good: Lightweight data generation
-public static IEnumerable GetTestUsers()
+public static IEnumerable GetLightweightTestUsers()
{
yield return new User { Id = 1, Name = "Test User 1" };
yield return new User { Id = 2, Name = "Test User 2" };
@@ -84,7 +83,7 @@ Remember that each `[Arguments(...)]` attribute produces exactly **one** test ca
// ❌ Bad: Combinatorial explosion via [Matrix]
[Test]
[MatrixDataSource]
-public void Process(
+public void ProcessAllCombinations(
[Matrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] int count,
[Matrix("a", "b", "c", "d", "e")] string label,
[Matrix(true, false)] bool flag)
@@ -97,7 +96,7 @@ public void Process(
[Arguments(1, "a", true)]
[Arguments(5, "c", false)]
[Arguments(10, "e", true)]
-public void Process(int count, string label, bool flag)
+public void ProcessTargetedCases(int count, string label, bool flag)
{
// Only 3 specific test cases — each [Arguments] attribute is one test
}
@@ -189,8 +188,8 @@ public class ExpensiveTests
[Before(Test)]
public async Task SetupEachTest()
{
- await StartDatabaseContainer();
- await MigrateDatabase();
+ await DatabaseInfrastructure.StartDatabaseContainer();
+ await DatabaseInfrastructure.MigrateDatabase();
}
}
@@ -202,8 +201,8 @@ public class EfficientTests
[Before(Class)]
public static async Task SetupOnce()
{
- _container = await StartDatabaseContainer();
- await MigrateDatabase();
+ _container = await DatabaseInfrastructure.StartDatabaseContainer();
+ await DatabaseInfrastructure.MigrateDatabase();
}
[After(Class)]
@@ -215,6 +214,19 @@ public class EfficientTests
}
}
}
+
+public sealed class DatabaseContainer : IAsyncDisposable
+{
+ public ValueTask DisposeAsync() => ValueTask.CompletedTask;
+}
+
+public static class DatabaseInfrastructure
+{
+ public static Task StartDatabaseContainer()
+ => Task.FromResult(new DatabaseContainer());
+
+ public static Task MigrateDatabase() => Task.CompletedTask;
+}
```
#### Use Lazy Initialization
@@ -232,6 +244,11 @@ public class PerformantTests
await resource.DoSomethingAsync();
}
}
+
+public sealed class ExpensiveResource
+{
+ public Task DoSomethingAsync() => Task.CompletedTask;
+}
```
### Optimize Assertions
diff --git a/docs/docs/guides/philosophy.md b/docs/docs/guides/philosophy.md
index d5a766a8464..df5ed673958 100644
--- a/docs/docs/guides/philosophy.md
+++ b/docs/docs/guides/philosophy.md
@@ -1,4 +1,3 @@
-
# Philosophy
@@ -10,7 +9,6 @@ Most frameworks make you opt into parallelism. TUnit flips that — tests run in
This also nudges you toward better test design. If your tests can't run in parallel, they're probably sharing state they shouldn't be. When they genuinely do need exclusive access to something (a shared file, a database, a hardware device), you opt out explicitly:
-
```csharp
[Test, NotInParallel]
public async Task ModifiesSharedConfigFile() { ... }
@@ -26,7 +24,6 @@ If you need shared state, use `static`. That makes the sharing visible to anyone
All assertions return `Task` and must be awaited. This is probably TUnit's most controversial decision.
-
```csharp
await Assert.That(result).IsEqualTo(expected);
```
@@ -49,7 +46,6 @@ The trade-off is that some older tools only work with VSTest — Coverlet being
TUnit's assertions are extension methods on specific types, not generic methods that accept anything. Intellisense only shows assertions that make sense for what you're testing. You can't accidentally check if a string is negative, because that method doesn't exist on strings.
-
```csharp
await Assert.That(user.Email)
.IsNotNull()
diff --git a/docs/docs/guides/report-aggregation.md b/docs/docs/guides/report-aggregation.md
index 6cc1e5881b6..f399d78b215 100644
--- a/docs/docs/guides/report-aggregation.md
+++ b/docs/docs/guides/report-aggregation.md
@@ -12,7 +12,7 @@ Report aggregation merges all of that into **one combined HTML report and one Gi
## How It Works
-1. Alongside every HTML report, TUnit writes a machine-readable sidecar: `{AssemblyName}-{os}-{tfm}.tunit-report.json`. This is on by default (disable with `TUNIT_DISABLE_JSON_REPORT=true`).
+1. Alongside every HTML report, TUnit writes a machine-readable sidecar: `{AssemblyName}-{os}-{tfm}.tunit-report.json`. This is on by default (disable with `TUNIT_DISABLE_JSON_REPORT=true` or `context.Settings.Reporting.JsonReportEnabled = false`).
2. With aggregation enabled, each test process also copies its sidecar into a directory shared by all sibling processes.
3. As each process finishes, it takes a cross-process lock, reads *all* sidecars present so far, and regenerates the merged HTML report and the summary block. The last process to finish naturally leaves the complete aggregate — no process ever needs to know whether it is the last one.
@@ -166,11 +166,11 @@ tunit-report merge --directory [options]
| --- | --- |
| `TUNIT_AGGREGATE_REPORTS` | Unset (default) — cooperative merge wherever a shared directory is resolvable (GitHub Actions, or explicit `TUNIT_AGGREGATE_DIR`); silently off otherwise. `defer` — persist sidecars + merged HTML only; no summary blocks (multi-step scenarios). `off` (also `false`/`0`/`no`/`disabled`/`none`) — no aggregation. |
| `TUNIT_AGGREGATE_DIR` | Shared directory for sidecars and the merged report. Required outside GitHub Actions; optional override on GitHub Actions. |
-| `TUNIT_DISABLE_JSON_REPORT` | Disables the JSON sidecar written next to the HTML report. Note: sidecars are what aggregation and `tunit-report` consume. |
+| `TUNIT_DISABLE_JSON_REPORT` | Disables the JSON sidecar written next to the HTML report. Programmatic equivalent: `context.Settings.Reporting.JsonReportEnabled = false`. Note: sidecars are what aggregation and `tunit-report` consume. |
## Notes & Limitations
-- Aggregation is driven by the HTML reporter's data pipeline — if you set `TUNIT_DISABLE_HTML_REPORTER`, no sidecars are produced and there is nothing to merge.
+- Aggregation is driven by the HTML reporter's data pipeline — if you set `TUNIT_DISABLE_HTML_REPORTER` or `context.Settings.Reporting.HtmlReportEnabled = false`, no sidecars are produced and there is nothing to merge.
- With cooperative mode (the default) across *multiple steps in the same job*, each step appends its own progressively-larger block (earlier steps' blocks can't be rewritten). Use `defer` + the tool for that layout, or `off` to restore per-suite blocks.
- Suites are identified per assembly + OS + TFM, so multi-targeted projects appear as separate rows (e.g. `MyTests (.NET 8.0.x)` / `MyTests (.NET 9.0.x)`).
- The GitHub step summary is capped at 1 MB by GitHub; the aggregated block replaces N per-suite blocks, so it usually *reduces* summary size.
diff --git a/docs/docs/migration/mstest.md b/docs/docs/migration/mstest.md
index bf100166999..daa40e99b06 100644
--- a/docs/docs/migration/mstest.md
+++ b/docs/docs/migration/mstest.md
@@ -1,4 +1,3 @@
-
# Migrating from MSTest
@@ -262,16 +261,16 @@ Assert.AreEqual(expected, actual);
Assert.AreNotEqual(expected, actual);
Assert.IsTrue(condition);
Assert.IsFalse(condition);
-Assert.IsNull(value);
-Assert.IsNotNull(value);
+Assert.IsNull(optional);
+Assert.IsNotNull(obj);
// TUnit
await Assert.That(actual).IsEqualTo(expected);
await Assert.That(actual).IsNotEqualTo(expected);
await Assert.That(condition).IsTrue();
await Assert.That(condition).IsFalse();
-await Assert.That(value).IsNull();
-await Assert.That(value).IsNotNull();
+await Assert.That(optional).IsNull();
+await Assert.That(obj).IsNotNull();
```
#### Reference Assertions
@@ -300,18 +299,18 @@ await Assert.That(value).IsNotAssignableTo();
```csharp
// MSTest
-CollectionAssert.AreEqual(expected, actual);
-CollectionAssert.AreNotEqual(expected, actual);
+CollectionAssert.AreEqual(values, numbers);
+CollectionAssert.AreNotEqual(values, otherNumbers);
CollectionAssert.Contains(collection, item);
CollectionAssert.DoesNotContain(collection, item);
-CollectionAssert.AllItemsAreNotNull(collection);
+CollectionAssert.AllItemsAreNotNull(objects);
// TUnit
-await Assert.That(actual).IsEquivalentTo(expected);
-await Assert.That(actual).IsNotEquivalentTo(expected);
+await Assert.That(numbers).IsEquivalentTo(values);
+await Assert.That(otherNumbers).IsNotEquivalentTo(values);
await Assert.That(collection).Contains(item);
await Assert.That(collection).DoesNotContain(item);
-await Assert.That(collection).All().Satisfy(item => item.IsNotNull());
+await Assert.That(objects).All().Satisfy(item => item.IsNotNull());
```
### String Assertions
@@ -321,7 +320,7 @@ await Assert.That(collection).All().Satisfy(item => item.IsNotNull());
StringAssert.Contains(text, substring);
StringAssert.StartsWith(text, prefix);
StringAssert.EndsWith(text, suffix);
-StringAssert.Matches(text, pattern);
+StringAssert.Matches(text, new Regex(pattern));
// TUnit
await Assert.That(text).Contains(substring);
@@ -334,11 +333,11 @@ await Assert.That(text).Matches(pattern);
```csharp
// MSTest
-Assert.ThrowsException(() => DoSomething());
-await Assert.ThrowsExceptionAsync(() => DoSomethingAsync());
+Assert.Throws(() => DoSomething());
+await Assert.ThrowsAsync(() => DoSomethingAsync());
// TUnit
-await Assert.ThrowsAsync(() => DoSomething());
+Assert.Throws(() => DoSomething());
await Assert.ThrowsAsync(() => DoSomethingAsync());
```
@@ -350,7 +349,7 @@ await Assert.ThrowsAsync(() => DoSomethingAsync());
[TestMethod]
[DataRow(1, 2, 3)]
[DataRow(10, 20, 30)]
-public void AdditionTest(int a, int b, int expected)
+public void MSTestAdditionTest(int a, int b, int expected)
{
Assert.AreEqual(expected, a + b);
}
@@ -359,7 +358,7 @@ public void AdditionTest(int a, int b, int expected)
[Test]
[Arguments(1, 2, 3)]
[Arguments(10, 20, 30)]
-public async Task AdditionTest(int a, int b, int expected)
+public async Task TUnitAdditionTest(int a, int b, int expected)
{
await Assert.That(a + b).IsEqualTo(expected);
}
@@ -369,13 +368,13 @@ public async Task AdditionTest(int a, int b, int expected)
```csharp
// MSTest
[TestMethod]
-[DynamicData(nameof(TestData), DynamicDataSourceType.Method)]
-public void TestMethod(int value, string text)
+[DynamicData(nameof(MSTestData), DynamicDataSourceType.Method)]
+public void MSTestMethod(int value, string text)
{
// Test implementation
}
-private static IEnumerable