Migrating from v2 to v3

As of: 2024 August 8 (0.2.0-pre.69)

This migration guide aims to be a comprehensive list helping developers migrate from xUnit.net v2 to v3. It includes information on how to upgrade your v2 projects to v3, what to expect for unit test authors, and what to expect for extensibility authors.

Because this is a comprehensive guide, you may wish to only skim parts of it, and use search functionality to find information on specific issues that arise, rather than trying to read the guide entirely. You should read the first informational section titled “Architectural Changes”, then follow the next three sections related to (a) updating NuGet packages, (b) updating to create an executable instead of a library, and (c) updating your target framework. All sections after that should be consider reference material.

In addition to this migration document (which only covers the differences between v2 and v3), we have a parallel document which covers what’s new in v3. The “What’s New” document describes newly available features (including information on the best way to create new xUnit.net v3 projects), and should be consulted after you’ve successfully migrated your project from v2 to v3.

The current builds are:

Package NuGet Version CI Version
xunit.v3.*
xunit.analyzers
xunit.runner.visualstudio

Note that while we attempt to ensure that CI builds are always usable, we cannot make guarantees. If you come across issues using a CI build, please let us know!

Table of Contents

Architectural Changes

Before we talk about the migration process, we need to provide information about architectural changes that have occurred between v2 and v3 that impact you as a developer.

New minimum runtime requirements

We have set new minimum runtime requirements for xUnit.net v3:

Our target frameworks for v3 currently are:

Also new for v3: Mono is officially supported on Linux and macOS for .NET Framework test projects. While it did often work with v1 and v2, we do officially test and verify with it now.

For a complete list of which packages target which frameworks, please see this issue.

Stand-alone executables

Test projects in v2 are library projects, and runners were required to run the tests. Those runners loaded the assemblies into their own address space and ran them from there. This was a design from back in .NET Framework days, where Application Domains could be used as a semi-effective isolation layer between the runner and the unit test code. This relatively complex isolation system was removed from .NET Core, so it’s no longer available. Application Domains not only solved a very real isolation problem (and loading/unloading problem), but also a much more significant dependency resolution problem.

Test projects in v3 are stand-alone executables now, capable of running themselves. Rather than worrying about dependency resolution at runtime, we allow the compiler to do its job for us. The isolation of running your tests in a separate process is significantly simpler and more effective than Application Domains were in .NET Framework, and works for .NET as well.

When you build a v3 test project, the result is directly executable.

If you run the executable without any command line options, all your tests will run:

You can pass -? to the executable for a complete list of command line switches that are available. The list will be very similar to the console runner’s command line options, except slightly reduced because of the fact that you’re only running a single test assembly.

If you want to build and run in a single step, dotnet run will work with both .NET Framework and .NET test projects. Just bear in mind that passing command line options with dotnet run requires prefixing options for the test project with --. For example, if you want to generate an XML report of the test run, these two are equivalent:

$ dotnet build
$ .\bin\Debug\TestProject.exe -xml results.xml
$ dotnet run -- -xml results.xml

Only SDK-style projects are supported

While we are aware that you may be able to make xUnit.net v3 work with older, pre-SDK-style projects, this is not a supported scenario by our team.

async void tests are no longer supported

Tests which are async void will be fast-failed at runtime to indicate that their signatures need to be updated from void to either Task or ValueTask.

Migrating to v3 Packages

Most of the packages for v3 Core Framework have moved to new names that start with xunit.v3.

Change the following package references (and use versions from the table at the top of this page):

v2 package v3 package
xunit xunit.v3
xunit.abstractions Remove, no longer required
xunit.analyzers Unchanged
xunit.assert xunit.v3.assert
xunit.assert.source xunit.v3.assert.source
xunit.console Remove, no longer supported
xunit.core xunit.v3.core
xunit.extensibility.core
xunit.extensibility.execution
xunit.v3.extensibility.core (*)
xunit.runner.console xunit.v3.runner.console
xunit.runner.msbuild xunit.v3.runner.msbuild
xunit.runner.reporters
xunit.runner.utility
xunit.v3.runner.utility (*)
xunit.runner.visualstudio Make sure to pick up a 3.x.y version

Note: In some cases multiple libraries/packages were merged together into a single new library/package, as denoted in the table above with (*).

If you want to use the MSBuild runner, we now ship both a .NET Framework and .NET Core/.NET version in the same package. It will dynamically select the correct version depending on whether you use the .NET Framework MSBuild or the .NET MSBuild (via dotnet build or dotnet msbuild). However, the .NET version only supports v3 test projects. If you need to still run v1 and/or v2 test projects, you must use the .NET Framework version. (Mono ships with a .NET Framework version of MSBuild, so all comments about .NET Framework also apply to Mono.)

Why did we change the package names?

We changed the package naming scheme from xunit.* to xunit.v3.* for two primary reasons and one secondary reason:

The secondary reason was:

Should extension authors follow this same strategy?

For extension authors, you have (at least) two choices for how to evolve your dependencies and package names:

The first option probably isn’t ideal, for the same reasons we’ve listed above. We suspect that following our strategy of new package names is probably the one that will lead users to the most success, because as they’re consciously choosing to upgrade xUnit.net from v2 to v3, they can also be consciously thinking about what that means for upgrading their extensions.

However, you know your users best, and there’s no one right answer here. It’s your decision which strategy makes the best sense for you and your users.

Convert to Executable Project

Update your project file (i.e., .csproj) and change OutputType from Library to Exe. You may need to add OutputType if it’s not present, since Library is the default value:

<PropertyGroup>
  <OutputType>Exe</OutputType>
</PropertyGroup>

Update Target Framework

Per the new minimum target framework versions, make sure to update your target framework(s) if you’re currently targeting something that’s no longer supported.

At this point, you should be able to successfully run dotnet restore.

From this point forward, we will discuss the changes you’ll see, and where common compilation errors might occur.

Removal of xunit.abstractions

The xunit.abstractions package in v2 was used to communicate across the Application Domain between unit tests and unit test runners. Now that the runner lives in the same process with your unit test project, this abstraction layer is no longer necessary.

There are several abstraction interfaces that were previously in the Xunit.Abstractions namespace that have been moved to new namespaces. Because xunit.v3.runner.utility still needs to link against xunit.abstractions to be able to run v2 tests, these types all needed to move into new namespaces to prevent naming collisions with the v2 types.

The follow types have been moved to Xunit (in xunit.v3.core):

The following types have been moved to Xunit.Runner.Common (in xunit.v3.runner.common):

The following types have been moved to Xunit.Sdk (in xunit.v3.common):

The following types have been moved to Xunit.v3 (in xunit.v3.core):

The following types have been removed:

Changes to Assertion Library

By and large, the assertion library in v3 is a small superset of the assertion library in v2 2.9. There are new assertions as well as overloads to existing assertions that might conflict with any custom assertions you’ve added and/or might cause compilation issues due to ambiguous overloads now available.

A complete list of what was added is available in the what’s new in v3 document.

Changes to Core Framework

The core framework has undergone some extension re-working internally. We aimed to make as few disruptive changes as possible, trying to limit those to where it was unavoidable and/or the usability improvement warranted the change. We hope that the vast majority of these will involve just modifying or cleaning up using statements.

Attributes that took type name and assembly name strings

There were several attributes in the system that allowed you to take type names as two strings: the fully qualified type name, and the name of the assembly where that type resides. This was to support source-based test discovery, which is a feature that has been removed from v3.

These attributes have been updated so that their constructors now simply take a Type, and you can use typeof to specify the type of the object.

For example:

[assembly: CollectionBehavior("MyNamespace.MyCollectionFactory", "MyAssembly")]

can be converted to:

[assembly: CollectionBehavior(typeof(MyCollectionFactory))]

The list of the affected attributes include:

Removal of reflection abstractions

The removal of reflection abstractions means that many previous splits between attributes and discoverers have been collapsed, and the attributes become responsible for their own behavior rather than relying on discoverers (which were previously written in terms of the reflection abstractions rather than depending on compiled code).

One obvious example of this is the removal of IDataDiscoverer (previously in Xunit.Abstractions) and DataDiscoverer (previously in Xunit.Sdk). The IDataDiscoverer.GetData method has been moved to IDataAttribute.GetData, and becomes an abstract method on the base DataAttribute class. The previous method was given an IAttributeInfo (which pointed to the DataAttribute-derived attribute) and IMethodInfo (which pointed to the test method); the updated method provides access to the MethodInfo (rather than the reflection abstraction version), and also to a disposal tracker so that it can add any data that it creates which might need to be disposed when cleaning up.

If you’re looking for a type that has disappeared and it’s a discoverer, chances are the thing it previous discovered is now responsible for describing itself, rather than relying on an external discoverer.

Namespace changes

Many of the namespace changes here have been done in the name of consistency. Generally speaking, when you see a type that now lives in the Xunit.Sdk namespace, it comes from xunit.v3.common, and when you see it in the Xunit.v3 namespace, it comes from xunit.v3.core.

Types removed

Miscellaneous changes

Changes to Runner Utility

The runner utility libraries have had a fairly extensive overhaul. The two previous libraries (xunit.runner.reporters and xunit.runner.utility) were merged into a single library (xunit.v3.runner.utility). Most of the types in this library have retained the Xunit namespace, just like they previously had. Some sub-namespaces have been introduced to isolate code that’s specific to running v1 vs. v2 vs. v3 tests.

A second new library (xunit.v3.runner.common) was introduced. Types in this library primarily have the Xunit.Runner.Common namespace.

The split between these two libraries comes from the fact that we now have an in-process console runner (that is the runner that is linked into your unit test projects, from the xunit.v3.runner.inproc.console package) and an out-of-process console runner (the one in xunit.v3.runner.console). The former only has to be able to run v3 test projects, whereas the latter has to be able to run projects from v1, v2, and v3.

The in-process console runner takes dependencies on xunit.v3.core and xunit.v3.runner.common to be able to perform its runner duties, whereas xunit.v3.runner.utility takes dependencies only on xunit.abstractions to be able run v2 tests (and Mono.Cecil to be able to read assembly metadata without loading the assembly into memory).

At the moment, third party reporters are not supported. We have an open issue to solve the problem of how to enable third party reporters without creating the strong dependency on xunit.v3.runner.utility, as the strong dependency in v2 on xunit.runner.utility made writing third party reporters exceptionally fragile.

Namespace changes

Types removed

Miscellaneous changes

Copyright © .NET Foundation. Contributions welcomed at https://github.com/xunit/xunit/tree/gh-pages.