Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
{"short-description":".NET skill guidance for xunit tasks"}
dotnet-xunit
xUnit v3 testing framework features for .NET. Covers [Fact] and [Theory] attributes, test fixtures (IClassFixture,
ICollectionFixture), parallel execution configuration, IAsyncLifetime for async setup/teardown, custom assertions,
and xUnit analyzers. Includes v2 compatibility notes where behavior differs.
Version assumptions: xUnit v3 primary (.NET 8.0+ baseline). Where v3 behavior differs from v2, compatibility notes
are provided inline. xUnit v2 remains widely used; many projects will encounter both versions during migration.
Scope
[Fact] and [Theory] test attributes and data sources
Test fixtures (IClassFixture, ICollectionFixture) and shared context
Parallel execution configuration and collection ordering
IAsyncLifetime for async setup/teardown
xUnit analyzers and custom assertions
xUnit v3 migration from v2 (TheoryDataRow, ValueTask lifecycle)
Out of scope
Test project scaffolding -- see [skill:dotnet-add-testing]
Testing strategy and test type decisions -- see [skill:dotnet-testing-strategy]
Integration testing patterns (WebApplicationFactory, Testcontainers) -- see [skill:dotnet-integration-testing]
Snapshot testing with Verify -- see [skill:dotnet-snapshot-testing]
Prerequisites: Test project already scaffolded via [skill:dotnet-add-testing] with xUnit packages referenced. Run
[skill:dotnet-version-detection] to confirm .NET 8.0+ baseline for xUnit v3 support.
Cross-references: [skill:dotnet-testing-strategy] for deciding what to test and how, [skill:dotnet-integration-testing]
for combining xUnit with WebApplicationFactory and Testcontainers.
xUnit v3 vs v2: Key Changes
Feature
xUnit v2
xUnit v3
Package
xunit (2.x)
xunit.v3
Runner
xunit.runner.visualstudio
xunit.runner.visualstudio (3.x)
Async lifecycle
IAsyncLifetime
IAsyncLifetime (now returns ValueTask)
Assert package
Bundled
Separate xunit.v3.assert (or xunit.v3.assert.source for extensibility)
Parallelism default
Per-collection
Per-collection (same, but configurable per-assembly)
Timeout
Timeout property on [Fact] and [Theory]
Timeout property on [Fact] and [Theory] (unchanged)
Removed in favor of custom assertions (v3.0); use Assert.Fail() for explicit messages
v2 compatibility note: If migrating from v2, replace xunit package with xunit.v3. Most [Fact] and [Theory]
tests work without changes. The primary migration effort is in IAsyncLifetime (return type changes to ValueTask),
[ClassData] (strongly typed row format), and removed assertion message parameters.
Facts and Theories
[Fact] -- Single Test Case
Use [Fact] for tests with no parameters:
publicclassDiscountCalculatorTests
{
[Fact]
publicvoidApply_NegativePercentage_ThrowsArgumentOutOfRangeException()
{
var calculator = new DiscountCalculator();
var ex = Assert.Throws<ArgumentOutOfRangeException>(
() => calculator.Apply(100m, percentage: -5));
Assert.Equal("percentage", ex.ParamName);
}
[Fact]
publicasync Task ApplyAsync_ValidDiscount_ReturnsDiscountedPrice()
{
var calculator = new DiscountCalculator();
var result = await calculator.ApplyAsync(100m, percentage: 15);
Assert.Equal(85m, result);
}
}
```text
### `[Theory]` -- Parameterized Tests
Use `[Theory]` to run the same test logic with different inputs.
#### `[InlineData]`
Best for simple value types:
```csharp
[Theory]
[InlineData(100, 10, 90)] // 10% off 100 = 90
[InlineData(200, 25, 150)] // 25% off 200 = 150
[InlineData(50, 0, 50)] // 0% off = no change
[InlineData(100, 100, 0)] // 100% off = 0publicvoidApply_VariousInputs_ReturnsExpectedPrice(
price, percentage, expected)
{
calculator = DiscountCalculator();
result = calculator.Apply(price, percentage);
Assert.Equal(expected, result);
}
```text
Best complex data shared datasets:
```csharp
{
TheoryData<Order, > ValidationCases => ()
{
{ Order { Items = [(, )], CustomerId = }, },
{ Order { Items = [], CustomerId = }, },
{ Order { Items = [(, )], CustomerId = }, },
};
[]
[]
{
validator = OrderValidator();
result = validator.IsValid(order);
Assert.Equal(expected, result);
}
}
```text
Best data shared across multiple test classes:
```csharp
: <<, , >>
{
IEnumerator<TheoryDataRow<, , >> GetEnumerator()
{
;
;
;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[]
[]
{
converter = CurrencyConverter();
rate = converter.GetRate(, to);
Assert.Equal(expectedRate, rate, precision: );
}
```text
---
Fixtures provide shared, expensive resources across tests maintaining test isolation.
{
ConnectionString { ; ; } = ;
{
ConnectionString = ;
ValueTask.CompletedTask;
}
{
ValueTask.CompletedTask;
}
}
: <>
{
DatabaseFixture _db;
{
_db = db;
}
[]
{
repo = OrderRepository(_db.ConnectionString);
result = repo.GetByIdAsync(KnownOrderId);
Assert.NotNull(result);
}
}
```text
**v2 compatibility note:** In xUnit v2, `IAsyncLifetime.InitializeAsync()` `DisposeAsync()` `Task`. In v3, they `ValueTask`. When migrating, change the types accordingly.
Use multiple test classes need the same expensive resource:
```csharp
[]
: <>
{
}
[]
{
DatabaseFixture _db;
{
_db = db;
}
[]
{
}
}
[]
{
DatabaseFixture _db;
{
_db = db;
}
}
```text
For per-test setup/teardown without a shared fixture:
```csharp
:
{
_tempDir = ;
{
_tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(_tempDir);
ValueTask.CompletedTask;
}
{
(Directory.Exists(_tempDir))
Directory.Delete(_tempDir, recursive: );
ValueTask.CompletedTask;
}
[]
{
filePath = Path.Combine(_tempDir, );
File.WriteAllTextAsync(filePath, );
processor = FileProcessor();
records = processor.ProcessAsync(filePath);
Assert.Equal(, records.Count);
}
}
```text
---
xUnit runs test classes within a collection sequentially but runs different collections parallel. Each test `[]` , .
###
####
:
```
[("", = )]
{ }
[]
{
}
```text
Create `xunit.runner.json` the test project root:
```json
{
: ,
: ,
: ,
:
}
```text
Ensure it copied to output:
```xml
<ItemGroup>
<Content Include= CopyToOutputDirectory= />
</ItemGroup>
```json
**v2 compatibility note:** In v2, configuration was via `xunit.runner.json` assembly attributes. v3 retains `xunit.runner.json` support the same property names.
---
Capture diagnostic output that appears test results:
```csharp
{
ITestOutputHelper _output;
{
_output = output;
}
[]
{
sw = Stopwatch.StartNew();
result = processor.ProcessBatchAsync(largeDataset);
sw.Stop();
_output.WriteLine();
Assert.True(sw.Elapsed < TimeSpan.FromSeconds());
}
}
```text
Bridge xUnit output to `Microsoft.Extensions.Logging` integration tests:
```csharp
:
{
ITestOutputHelper _output;
=> _output = output;
=>
XunitLogger(_output, categoryName);
{ }
}
{
IDisposable? BeginScope<TState>(TState state) TState : => ;
=> ;
{
output.WriteLine();
(exception )
output.WriteLine(exception.ToString());
}
}
```text
---
Create domain-specific assertions cleaner test code:
```csharp
{
{
Assert.NotNull(order);
(order.Status != expected)
{
Xunit.Sdk.EqualException.ForMismatchedValues(
expected, order.Status);
}
}
{
Assert.NotNull(order);
item = Assert.Single(order.Items, i => i.Sku == sku);
Assert.Equal(quantity, item.Quantity);
}
}
[]
{
order = Order();
order.Complete();
OrderAssert.HasStatus(order, OrderStatus.Completed);
}
```text
Group related assertions so all are evaluated even one fails:
```csharp
[]
{
order = OrderFactory.Create(request);
Assert.Multiple(
() => Assert.Equal(, order.CustomerId),
() => Assert.Equal(OrderStatus.Pending, order.Status),
() => Assert.NotEqual(Guid.Empty, order.Id),
() => Assert.NotEmpty(order.Items)
);
}
```text
**v2 compatibility note:** `Assert.Multiple` xUnit v3. In v2, use separate assertions -- the test stops at the first failure.
---
The `xunit.analyzers` package (included xUnit v3) catches common test authoring mistakes at compile time.
| Rule | Description | Severity |
|------|-------------|----------|
| `xUnit1004` | Test methods should be skipped | Info |
| `xUnit1012` | Null should be used type parameters | Warning |
| `xUnit1025` | `InlineData` should be unique within a `Theory` | Warning |
| `xUnit2000` | Constants literals should be the expected argument | Warning |
| `xUnit2002` | Do use check type | Warning |
| `xUnit2007` | Do use `` expression to check type | Warning |
| `xUnit2013` | Do use equality check to check collection size | Warning |
| `xUnit2017` | Do use `Contains()` to check exists a | Warning |
In `.editorconfig` test projects:
```ini
[]
dotnet_diagnostic.xUnit1004.severity = suggestion
```csharp
---
- **One fact per `[Fact]`, one concept per `[Theory]`.** If a `[Theory]` tests fundamentally different scenarios, split separate `[Fact]` methods.
- **Use `IClassFixture` expensive shared resources** within a single test . Use `ICollectionFixture` multiple classes share the same resource.
- **Do disable parallelism globally.** Instead, tests that share mutable state named collections.
- **Use `IAsyncLifetime` setup/teardown** instead of constructors `IDisposable`. Constructors cannot be , `IDisposable.Dispose()` does .
- **Keep test data close to the test.** Prefer `[InlineData]` simple cases. Use `[MemberData]` `[ClassData]` only data complex shared.
- **Enable xUnit analyzers** all test projects. They common mistakes that lead to -passing flaky tests.
---
**Do use constructor-injected `ITestOutputHelper` methods.** `ITestOutputHelper` per-test-instance; store it an instance field, a one.
**Do forget to make fixture classes ``.**
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
Find definitions: serena_find_symbol instead of text search
Understand structure: serena_get_symbols_overview for file organization
Track references: serena_find_referencing_symbols for impact analysis
Precise edits: serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
Use Serena: Navigation, refactoring, dependency analysis, precise edits
Use Read/Grep: Reading full files, pattern matching, simple text operations
Fallback: If Serena unavailable, traditional tools work fine
publicvoidConvert_KnownPairs_ReturnsExpectedRate(stringfrom, string to, decimal expectedRate)
var
new
var
from
2
## Fixtures: Shared Setup and Teardown
while
### `IClassFixture<T>` -- Shared Per Test Class
Use when multiple tests in the same class share an expensive resource (database connection, configuration):
```csharp
publicclass DatabaseFixture : IAsyncLifetime
xUnit requires fixture types to be publicwith a public parameterless constructor (or `IAsyncLifetime`). Non-public fixtures cause silent failures.
3. **Do not mix `[Fact]` and `[Theory]` on the same method.** A method is either a fact or a theory, not both.
4. **Do notreturn `void` fromasync test methods.** Return `Task` or `ValueTask`. `asyncvoid` tests report false success because xUnit cannot observe the async completion.
5. **Do not use `[Collection]` without a matching `[CollectionDefinition]`.** An unmatched collection name silently creates an implicit collection withdefault behavior, defeating the purpose.
---
## References
- [xUnit Documentation](https://xunit.net/)
- [xUnit v3 migration guide](https://xunit.net/docs/getting-started/v3/migration)
- [xUnit analyzers](https://xunit.net/xunit.analyzers/rules/)
- [Shared context in xUnit](https://xunit.net/docs/shared-context)
- [Configuring xUnit with JSON](https://xunit.net/docs/configuration-files)