Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.
xUnit Upgrade Guide: From 2.9.x to 3.x
Applicable Scenarios
Use this skill when asked to perform the following tasks:
- Upgrade existing xUnit 2.x test projects to xUnit 3.x
- Evaluate the scope of impact for xUnit upgrade
- Resolve compilation errors during xUnit upgrade
- Use xUnit 3.x new features to improve tests
Core Concepts
Package Naming Changes
xUnit v3 adopts a new package naming strategy:
| v1~v2 Package Name |
v3 Package Name |
Description |
xunit |
xunit.v3 |
Main test framework |
xunit.assert |
xunit.v3.assert |
Assertion library |
xunit.core |
xunit.v3.core |
Core components |
xunit.abstractions |
(removed) |
No longer needed |
xunit.runner.visualstudio |
xunit.runner.visualstudio (3.x.y) |
Test runner |
Important: Use xunit.v3 package name, not xunit.
Minimum Runtime Requirements
xUnit 3.x strict requirements:
- .NET Framework 4.7.2+ or
- .NET 8.0+ (recommended)
Unsupported versions:
- .NET Core 3.1
- .NET 5, 6, 7
Breaking Changes List
1. Test Project Becomes Executable
<!-- xUnit 2.x (Library) -->
<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>
<!-- xUnit 3.x (Exe) - Must change -->
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
```text
### 2. async void Tests No Longer Supported
```csharp
// ❌ xUnit 2.x - fails in 3.x
[Fact]
public async void TestSomeAsyncFunction()
{
var result = await SomeAsyncMethod();
Assert.True(result);
}
// ✅ xUnit 3.x - correct way
[Fact]
public async Task TestSomeAsyncFunction()
{
var result = await SomeAsyncMethod();
Assert.True(result);
}
```text
### 3. IAsyncLifetime Changes
In xUnit 3.x, `IAsyncLifetime` inherits `IAsyncDisposable`. If implementing both `IAsyncLifetime` and `IDisposable`, only `DisposeAsync` will be called, not `Dispose`.
```csharp
// ⚠️ Pattern to note
public class MyTestClass : IAsyncLifetime, IDisposable
{
public async Task InitializeAsync() { /* ... */ }
public async Task DisposeAsync() { /* will be called */ }
public void Dispose() { /* will NOT be called in 3.x */ }
}
// ✅ Recommended: put all cleanup logic in DisposeAsync
public class MyTestClass : IAsyncLifetime
{
public async Task InitializeAsync() { /* initialization */ }
public async Task DisposeAsync() { /* all cleanup logic */ }
}
```text
### 4. SkippableFact/SkippableTheory Removed
```csharp
// ❌ xUnit 2.x - removed
[SkippableFact]
public void SkippableTest()
{
Skip.If(someCondition, "Skip reason");
// test logic
}
// ✅ xUnit 3.x - use Assert.Skip
[Fact]
public void SkippableTest()
{
if (someCondition)
{
Assert.Skip("Skip reason");
}
// test logic
}
```text
### 5. SDK-style Projects Only
Check if project file starts with:
```xml
<Project Sdk="Microsoft.NET.Sdk">
```text
If traditional format, must convert to SDK-style first.
---
## Upgrade Steps
### Step 1: Create Upgrade Branch
```bash
git checkout -b feature/upgrade-xunit-v3
```text
### Step 2: Update Project Files
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<!-- xUnit v3 packages -->
<PackageReference Include="xunit.v3" Version="3.0.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<!-- Common helper packages -->
<PackageReference Include="AwesomeAssertions" Version="8.1.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
</ItemGroup>
</Project>
```text
### Step 3: Fix async void Tests
Use IDE search:
```regex
async\s+void.*\[(Fact|Theory)\]
```text
Change all `async void` to `async Task`.
### Step 4: Update using Statements
```csharp
// Remove (no longer needed)
// using Xunit.Abstractions;
// Keep
using Xunit;
```text
### Step 5: Compile and Test
```bash
dotnet clean
dotnet restore
dotnet build
dotnet test --verbosity normal
```text
---
## xUnit 3.x New Features
### Dynamic Skip Tests
**Declarative (SkipUnless/SkipWhen)**:
```csharp
[Fact(SkipUnless = nameof(IsWindowsEnvironment),
Skip = "This test only runs on Windows")]
public void OnlyRunOnWindowsTest()
{
// test logic
}
public static bool IsWindowsEnvironment =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
```text
**Imperative (Assert.Skip)**:
```csharp
[Fact]
public void SkipBasedOnEnvironmentVariableTest()
{
var enableTests = Environment.GetEnvironmentVariable("ENABLE_INTEGRATION_TESTS");
if (string.IsNullOrEmpty(enableTests) || enableTests.ToLower() != "true")
{
Assert.Skip("Integration tests disabled. Set ENABLE_INTEGRATION_TESTS=true to run");
}
// test logic...
}
```text
### Explicit Tests
```csharp
[Fact(Explicit = true)]
public void ExpensiveIntegrationTest()
{
// This test won't run by default unless explicitly requested
// Suitable for performance tests, long-running tests
}
```text
### [Test] Attribute
```csharp
// All three ways have same functionality
[Test]
public void UsingTestAttributeTest() { Assert.True(true); }
[Fact]
public void UsingFactAttributeTest() { Assert.True(true); }
```text
### Matrix Theory Data
```csharp
public static TheoryData<int, string> TestData =>
new MatrixTheoryData<int, string>(
[1, 2, 3], // number data
["Hello", "World", "Test"] // string data
);
// This generates 3×3=9 test cases
[Theory]
[MemberData(nameof(TestData))]
public void MatrixTestExample(int number, string text)
{
number.Should().BePositive();
text.Should().NotBeNullOrEmpty();
}
```text
### Assembly Fixtures
```csharp
public class DatabaseAssemblyFixture : IAsyncLifetime
{
public string ConnectionString { get; private set; }
public async Task InitializeAsync()
{
// create test database
ConnectionString = await CreateTestDatabaseAsync();
}
public async Task DisposeAsync()
{
// cleanup test database
await DropTestDatabaseAsync();
}
}
// Register Assembly Fixture
[assembly: AssemblyFixture(typeof(DatabaseAssemblyFixture))]
// Use in tests
public class UserServiceTests
{
private readonly DatabaseAssemblyFixture _dbFixture;
public UserServiceTests(DatabaseAssemblyFixture dbFixture)
{
_dbFixture = dbFixture;
}
[Fact]
public void Test1() { /* use _dbFixture.ConnectionString */ }
}
```text
### Test Pipeline Startup
```csharp
public class TestPipelineStartup : ITestPipelineStartup
{
public async Task ConfigureAsync(ITestPipelineBuilder builder,
CancellationToken cancellationToken)
{
// global initialization logic
Console.WriteLine("Initializing test environment...");
await InitializeDatabaseAsync();
}
}
// Register
[assembly: TestPipelineStartup(typeof(TestPipelineStartup))]
```text
---
## xunit.runner.json Configuration
```json
{
"$schema": "https://xunit.net/schema/v3/xunit.runner.schema.json",
"parallelAlgorithm": "conservative",
"maxParallelThreads": 4,
"diagnosticMessages": true,
"internalDiagnosticMessages": false,
"methodDisplay": "classAndMethod",
"preEnumerateTheories": true,
"stopOnFail": false
}
```text
---
## Test Report Formats
xUnit 3.x supports multiple report formats:
```bash
# Generate CTRF format report
dotnet run -- -ctrf results.json
# Generate TRX format report
dotnet run -- -trx results.trx
# Generate XML format report
dotnet run -- -xml results.xml
# Generate multiple format reports
dotnet run -- -xml results.xml -ctrf results.json -trx results.trx
```text
---
## Common Issues and Solutions
### Issue 1: xunit.abstractions not found
## Detailed Examples
See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.
1---2name: dotnet-testing-advanced-xunit-upgrade-guide3description: Complete guide for upgrading xUnit 2.9.x to 3.x. Use when upgrading xUnit v2 to v3 or understanding xUnit v3 new features and breaking changes. Covers package updates, async void fixes, IAsyncLifetime adjustments. Includes new features: Assert.Skip, Explicit Tests, Matrix Theory, Assembly Fixtures. Keywords: xunit upgrade, xunit v3, xunit 3.x, xunit migration, xunit upgrade, xunit.v3, OutputType Exe, IAsyncLifetime v3, Assert.Skip, SkipUnless, SkipWhen, Explicit attribute, MatrixTheoryData, AssemblyFixture, breaking changes4license: MIT5---67Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.89# xUnit Upgrade Guide: From 2.9.x to 3.x1011## Applicable Scenarios1213Use this skill when asked to perform the following tasks:1415- Upgrade existing xUnit 2.x test projects to xUnit 3.x16- Evaluate the scope of impact for xUnit upgrade17- Resolve compilation errors during xUnit upgrade18- Use xUnit 3.x new features to improve tests1920## Core Concepts2122### Package Naming Changes2324xUnit v3 adopts a new package naming strategy:2526| v1~v2 Package Name | v3 Package Name | Description |27| --------------------------- | ----------------------------------- | ------------------- |28| `xunit` | `xunit.v3` | Main test framework |29| `xunit.assert` | `xunit.v3.assert` | Assertion library |30| `xunit.core` | `xunit.v3.core` | Core components |31| `xunit.abstractions` | (removed) | No longer needed |32| `xunit.runner.visualstudio` | `xunit.runner.visualstudio` (3.x.y) | Test runner |3334**Important**: Use `xunit.v3` package name, not `xunit`.3536### Minimum Runtime Requirements3738xUnit 3.x strict requirements:3940- **.NET Framework 4.7.2+** or41- **.NET 8.0+** (recommended)4243**Unsupported versions**:4445- .NET Core 3.146- .NET 5, 6, 74748---4950## Breaking Changes List5152### 1. Test Project Becomes Executable5354````xml55<!-- xUnit 2.x (Library) -->56<PropertyGroup>57 <OutputType>Library</OutputType>58</PropertyGroup>5960<!-- xUnit 3.x (Exe) - Must change -->61<PropertyGroup>62 <OutputType>Exe</OutputType>63</PropertyGroup>64```text6566### 2. async void Tests No Longer Supported6768```csharp69// ❌ xUnit 2.x - fails in 3.x70[Fact]71public async void TestSomeAsyncFunction()72{73 var result = await SomeAsyncMethod();74 Assert.True(result);75}7677// ✅ xUnit 3.x - correct way78[Fact]79public async Task TestSomeAsyncFunction()80{81 var result = await SomeAsyncMethod();82 Assert.True(result);83}84```text8586### 3. IAsyncLifetime Changes8788In xUnit 3.x, `IAsyncLifetime` inherits `IAsyncDisposable`. If implementing both `IAsyncLifetime` and `IDisposable`, only `DisposeAsync` will be called, not `Dispose`.8990```csharp91// ⚠️ Pattern to note92public class MyTestClass : IAsyncLifetime, IDisposable93{94 public async Task InitializeAsync() { /* ... */ }95 public async Task DisposeAsync() { /* will be called */ }96 public void Dispose() { /* will NOT be called in 3.x */ }97}9899// ✅ Recommended: put all cleanup logic in DisposeAsync100public class MyTestClass : IAsyncLifetime101{102 public async Task InitializeAsync() { /* initialization */ }103 public async Task DisposeAsync() { /* all cleanup logic */ }104}105```text106107### 4. SkippableFact/SkippableTheory Removed108109```csharp110// ❌ xUnit 2.x - removed111[SkippableFact]112public void SkippableTest()113{114 Skip.If(someCondition, "Skip reason");115 // test logic116}117118// ✅ xUnit 3.x - use Assert.Skip119[Fact]120public void SkippableTest()121{122 if (someCondition)123 {124 Assert.Skip("Skip reason");125 }126 // test logic127}128```text129130### 5. SDK-style Projects Only131132Check if project file starts with:133134```xml135<Project Sdk="Microsoft.NET.Sdk">136```text137138If traditional format, must convert to SDK-style first.139140---141142## Upgrade Steps143144### Step 1: Create Upgrade Branch145146```bash147git checkout -b feature/upgrade-xunit-v3148```text149150### Step 2: Update Project Files151152```xml153<Project Sdk="Microsoft.NET.Sdk">154 <PropertyGroup>155 <TargetFramework>net8.0</TargetFramework>156 <OutputType>Exe</OutputType>157 <ImplicitUsings>enable</ImplicitUsings>158 <Nullable>enable</Nullable>159 <IsPackable>false</IsPackable>160 <IsTestProject>true</IsTestProject>161 </PropertyGroup>162163 <ItemGroup>164 <!-- xUnit v3 packages -->165 <PackageReference Include="xunit.v3" Version="3.0.1" />166 <PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">167 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>168 <PrivateAssets>all</PrivateAssets>169 </PackageReference>170 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />171172 <!-- Common helper packages -->173 <PackageReference Include="AwesomeAssertions" Version="8.1.0" />174 <PackageReference Include="NSubstitute" Version="5.3.0" />175 </ItemGroup>176</Project>177```text178179### Step 3: Fix async void Tests180181Use IDE search:182183```regex184async\s+void.*\[(Fact|Theory)\]185```text186187Change all `async void` to `async Task`.188189### Step 4: Update using Statements190191```csharp192// Remove (no longer needed)193// using Xunit.Abstractions;194195// Keep196using Xunit;197```text198199### Step 5: Compile and Test200201```bash202dotnet clean203dotnet restore204dotnet build205dotnet test --verbosity normal206```text207208---209210## xUnit 3.x New Features211212### Dynamic Skip Tests213214**Declarative (SkipUnless/SkipWhen)**:215216```csharp217[Fact(SkipUnless = nameof(IsWindowsEnvironment),218 Skip = "This test only runs on Windows")]219public void OnlyRunOnWindowsTest()220{221 // test logic222}223224public static bool IsWindowsEnvironment =>225 RuntimeInformation.IsOSPlatform(OSPlatform.Windows);226```text227228**Imperative (Assert.Skip)**:229230```csharp231[Fact]232public void SkipBasedOnEnvironmentVariableTest()233{234 var enableTests = Environment.GetEnvironmentVariable("ENABLE_INTEGRATION_TESTS");235236 if (string.IsNullOrEmpty(enableTests) || enableTests.ToLower() != "true")237 {238 Assert.Skip("Integration tests disabled. Set ENABLE_INTEGRATION_TESTS=true to run");239 }240241 // test logic...242}243```text244245### Explicit Tests246247```csharp248[Fact(Explicit = true)]249public void ExpensiveIntegrationTest()250{251 // This test won't run by default unless explicitly requested252 // Suitable for performance tests, long-running tests253}254```text255256### [Test] Attribute257258```csharp259// All three ways have same functionality260[Test]261public void UsingTestAttributeTest() { Assert.True(true); }262263[Fact]264public void UsingFactAttributeTest() { Assert.True(true); }265```text266267### Matrix Theory Data268269```csharp270public static TheoryData<int, string> TestData =>271 new MatrixTheoryData<int, string>(272 [1, 2, 3], // number data273 ["Hello", "World", "Test"] // string data274 );275 // This generates 3×3=9 test cases276277[Theory]278[MemberData(nameof(TestData))]279public void MatrixTestExample(int number, string text)280{281 number.Should().BePositive();282 text.Should().NotBeNullOrEmpty();283}284```text285286### Assembly Fixtures287288```csharp289public class DatabaseAssemblyFixture : IAsyncLifetime290{291 public string ConnectionString { get; private set; }292293 public async Task InitializeAsync()294 {295 // create test database296 ConnectionString = await CreateTestDatabaseAsync();297 }298299 public async Task DisposeAsync()300 {301 // cleanup test database302 await DropTestDatabaseAsync();303 }304}305306// Register Assembly Fixture307[assembly: AssemblyFixture(typeof(DatabaseAssemblyFixture))]308309// Use in tests310public class UserServiceTests311{312 private readonly DatabaseAssemblyFixture _dbFixture;313314 public UserServiceTests(DatabaseAssemblyFixture dbFixture)315 {316 _dbFixture = dbFixture;317 }318319 [Fact]320 public void Test1() { /* use _dbFixture.ConnectionString */ }321}322```text323324### Test Pipeline Startup325326```csharp327public class TestPipelineStartup : ITestPipelineStartup328{329 public async Task ConfigureAsync(ITestPipelineBuilder builder,330 CancellationToken cancellationToken)331 {332 // global initialization logic333 Console.WriteLine("Initializing test environment...");334 await InitializeDatabaseAsync();335 }336}337338// Register339[assembly: TestPipelineStartup(typeof(TestPipelineStartup))]340```text341342---343344## xunit.runner.json Configuration345346```json347{348 "$schema": "https://xunit.net/schema/v3/xunit.runner.schema.json",349 "parallelAlgorithm": "conservative",350 "maxParallelThreads": 4,351 "diagnosticMessages": true,352 "internalDiagnosticMessages": false,353 "methodDisplay": "classAndMethod",354 "preEnumerateTheories": true,355 "stopOnFail": false356}357```text358359---360361## Test Report Formats362363xUnit 3.x supports multiple report formats:364365```bash366# Generate CTRF format report367dotnet run -- -ctrf results.json368369# Generate TRX format report370dotnet run -- -trx results.trx371372# Generate XML format report373dotnet run -- -xml results.xml374375# Generate multiple format reports376dotnet run -- -xml results.xml -ctrf results.json -trx results.trx377```text378379---380381## Common Issues and Solutions382383### Issue 1: xunit.abstractions not found384385## Detailed Examples386387See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.