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-guide3category: testing4subcategory: fundamentals5description: |6 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.7 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 changes8targets: ['*']9license: MIT10metadata:11 author: Kevin Tseng12 version: '1.0.0'13 tags: 'xunit, upgrade, migration, v3, breaking-changes, testing-framework'14 related_skills: 'xunit-project-setup, unit-test-fundamentals'15claudecode: {}16opencode: {}17codexcli:18 short-description: '.NET skill guidance for dotnet-testing-advanced-xunit-upgrade-guide'19copilot: {}20geminicli: {}21antigravity: {}22---2324Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.2526# xUnit Upgrade Guide: From 2.9.x to 3.x2728## Applicable Scenarios2930Use this skill when asked to perform the following tasks:3132- Upgrade existing xUnit 2.x test projects to xUnit 3.x33- Evaluate the scope of impact for xUnit upgrade34- Resolve compilation errors during xUnit upgrade35- Use xUnit 3.x new features to improve tests3637## Core Concepts3839### Package Naming Changes4041xUnit v3 adopts a new package naming strategy:4243| v1~v2 Package Name | v3 Package Name | Description |44| --------------------------- | ----------------------------------- | ------------------- |45| `xunit` | `xunit.v3` | Main test framework |46| `xunit.assert` | `xunit.v3.assert` | Assertion library |47| `xunit.core` | `xunit.v3.core` | Core components |48| `xunit.abstractions` | (removed) | No longer needed |49| `xunit.runner.visualstudio` | `xunit.runner.visualstudio` (3.x.y) | Test runner |5051**Important**: Use `xunit.v3` package name, not `xunit`.5253### Minimum Runtime Requirements5455xUnit 3.x strict requirements:5657- **.NET Framework 4.7.2+** or58- **.NET 8.0+** (recommended)5960**Unsupported versions**:6162- .NET Core 3.163- .NET 5, 6, 76465---6667## Breaking Changes List6869### 1. Test Project Becomes Executable7071````xml72<!-- xUnit 2.x (Library) -->73<PropertyGroup>74 <OutputType>Library</OutputType>75</PropertyGroup>7677<!-- xUnit 3.x (Exe) - Must change -->78<PropertyGroup>79 <OutputType>Exe</OutputType>80</PropertyGroup>81```text8283### 2. async void Tests No Longer Supported8485```csharp86// ❌ xUnit 2.x - fails in 3.x87[Fact]88public async void TestSomeAsyncFunction()89{90 var result = await SomeAsyncMethod();91 Assert.True(result);92}9394// ✅ xUnit 3.x - correct way95[Fact]96public async Task TestSomeAsyncFunction()97{98 var result = await SomeAsyncMethod();99 Assert.True(result);100}101```text102103### 3. IAsyncLifetime Changes104105In xUnit 3.x, `IAsyncLifetime` inherits `IAsyncDisposable`. If implementing both `IAsyncLifetime` and `IDisposable`, only `DisposeAsync` will be called, not `Dispose`.106107```csharp108// ⚠️ Pattern to note109public class MyTestClass : IAsyncLifetime, IDisposable110{111 public async Task InitializeAsync() { /* ... */ }112 public async Task DisposeAsync() { /* will be called */ }113 public void Dispose() { /* will NOT be called in 3.x */ }114}115116// ✅ Recommended: put all cleanup logic in DisposeAsync117public class MyTestClass : IAsyncLifetime118{119 public async Task InitializeAsync() { /* initialization */ }120 public async Task DisposeAsync() { /* all cleanup logic */ }121}122```text123124### 4. SkippableFact/SkippableTheory Removed125126```csharp127// ❌ xUnit 2.x - removed128[SkippableFact]129public void SkippableTest()130{131 Skip.If(someCondition, "Skip reason");132 // test logic133}134135// ✅ xUnit 3.x - use Assert.Skip136[Fact]137public void SkippableTest()138{139 if (someCondition)140 {141 Assert.Skip("Skip reason");142 }143 // test logic144}145```text146147### 5. SDK-style Projects Only148149Check if project file starts with:150151```xml152<Project Sdk="Microsoft.NET.Sdk">153```text154155If traditional format, must convert to SDK-style first.156157---158159## Upgrade Steps160161### Step 1: Create Upgrade Branch162163```bash164git checkout -b feature/upgrade-xunit-v3165```text166167### Step 2: Update Project Files168169```xml170<Project Sdk="Microsoft.NET.Sdk">171 <PropertyGroup>172 <TargetFramework>net8.0</TargetFramework>173 <OutputType>Exe</OutputType>174 <ImplicitUsings>enable</ImplicitUsings>175 <Nullable>enable</Nullable>176 <IsPackable>false</IsPackable>177 <IsTestProject>true</IsTestProject>178 </PropertyGroup>179180 <ItemGroup>181 <!-- xUnit v3 packages -->182 <PackageReference Include="xunit.v3" Version="3.0.1" />183 <PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">184 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>185 <PrivateAssets>all</PrivateAssets>186 </PackageReference>187 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />188189 <!-- Common helper packages -->190 <PackageReference Include="AwesomeAssertions" Version="8.1.0" />191 <PackageReference Include="NSubstitute" Version="5.3.0" />192 </ItemGroup>193</Project>194```text195196### Step 3: Fix async void Tests197198Use IDE search:199200```regex201async\s+void.*\[(Fact|Theory)\]202```text203204Change all `async void` to `async Task`.205206### Step 4: Update using Statements207208```csharp209// Remove (no longer needed)210// using Xunit.Abstractions;211212// Keep213using Xunit;214```text215216### Step 5: Compile and Test217218```bash219dotnet clean220dotnet restore221dotnet build222dotnet test --verbosity normal223```text224225---226227## xUnit 3.x New Features228229### Dynamic Skip Tests230231**Declarative (SkipUnless/SkipWhen)**:232233```csharp234[Fact(SkipUnless = nameof(IsWindowsEnvironment),235 Skip = "This test only runs on Windows")]236public void OnlyRunOnWindowsTest()237{238 // test logic239}240241public static bool IsWindowsEnvironment =>242 RuntimeInformation.IsOSPlatform(OSPlatform.Windows);243```text244245**Imperative (Assert.Skip)**:246247```csharp248[Fact]249public void SkipBasedOnEnvironmentVariableTest()250{251 var enableTests = Environment.GetEnvironmentVariable("ENABLE_INTEGRATION_TESTS");252253 if (string.IsNullOrEmpty(enableTests) || enableTests.ToLower() != "true")254 {255 Assert.Skip("Integration tests disabled. Set ENABLE_INTEGRATION_TESTS=true to run");256 }257258 // test logic...259}260```text261262### Explicit Tests263264```csharp265[Fact(Explicit = true)]266public void ExpensiveIntegrationTest()267{268 // This test won't run by default unless explicitly requested269 // Suitable for performance tests, long-running tests270}271```text272273### [Test] Attribute274275```csharp276// All three ways have same functionality277[Test]278public void UsingTestAttributeTest() { Assert.True(true); }279280[Fact]281public void UsingFactAttributeTest() { Assert.True(true); }282```text283284### Matrix Theory Data285286```csharp287public static TheoryData<int, string> TestData =>288 new MatrixTheoryData<int, string>(289 [1, 2, 3], // number data290 ["Hello", "World", "Test"] // string data291 );292 // This generates 3×3=9 test cases293294[Theory]295[MemberData(nameof(TestData))]296public void MatrixTestExample(int number, string text)297{298 number.Should().BePositive();299 text.Should().NotBeNullOrEmpty();300}301```text302303### Assembly Fixtures304305```csharp306public class DatabaseAssemblyFixture : IAsyncLifetime307{308 public string ConnectionString { get; private set; }309310 public async Task InitializeAsync()311 {312 // create test database313 ConnectionString = await CreateTestDatabaseAsync();314 }315316 public async Task DisposeAsync()317 {318 // cleanup test database319 await DropTestDatabaseAsync();320 }321}322323// Register Assembly Fixture324[assembly: AssemblyFixture(typeof(DatabaseAssemblyFixture))]325326// Use in tests327public class UserServiceTests328{329 private readonly DatabaseAssemblyFixture _dbFixture;330331 public UserServiceTests(DatabaseAssemblyFixture dbFixture)332 {333 _dbFixture = dbFixture;334 }335336 [Fact]337 public void Test1() { /* use _dbFixture.ConnectionString */ }338}339```text340341### Test Pipeline Startup342343```csharp344public class TestPipelineStartup : ITestPipelineStartup345{346 public async Task ConfigureAsync(ITestPipelineBuilder builder,347 CancellationToken cancellationToken)348 {349 // global initialization logic350 Console.WriteLine("Initializing test environment...");351 await InitializeDatabaseAsync();352 }353}354355// Register356[assembly: TestPipelineStartup(typeof(TestPipelineStartup))]357```text358359---360361## xunit.runner.json Configuration362363```json364{365 "$schema": "https://xunit.net/schema/v3/xunit.runner.schema.json",366 "parallelAlgorithm": "conservative",367 "maxParallelThreads": 4,368 "diagnosticMessages": true,369 "internalDiagnosticMessages": false,370 "methodDisplay": "classAndMethod",371 "preEnumerateTheories": true,372 "stopOnFail": false373}374```text375376---377378## Test Report Formats379380xUnit 3.x supports multiple report formats:381382```bash383# Generate CTRF format report384dotnet run -- -ctrf results.json385386# Generate TRX format report387dotnet run -- -trx results.trx388389# Generate XML format report390dotnet run -- -xml results.xml391392# Generate multiple format reports393dotnet run -- -xml results.xml -ctrf results.json -trx results.trx394```text395396---397398## Common Issues and Solutions399400### Issue 1: xunit.abstractions not found401402## Detailed Examples403404See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.