---
title: "xUnit v3 and some stuff about TUnit"
description: "I know that there is a lot of fuse about [**TUnit**](https://github.com/thomhurst/TUnit) and I am here writing about xUnit. I might cover TUnit in the future, but for now, the topic is the `v3` prerelease of xUnit!"
updated: 2024-09-30
tags: ["xUnit", ".NET", "C#"]
canonical: "https://steven-giesel.com/blogPost/669d5aa2-b86a-4096-948c-9a767b7071e3/xunit-v3-and-some-stuff-about-tunit"
---

# xUnit v3 and some stuff about TUnit

I know that there is a lot of fuse about [**TUnit**](https://github.com/thomhurst/TUnit) and I am here writing about xUnit. I might cover TUnit in the future, but for now, the topic is the `v3` prerelease of xUnit!

## v3
`v3` is in the making since a while now and it is still in the prerelease stage. The main goal of `v3` is to drop old stuff that was needed in the "good old days" and to make the framework more modern. I recently started to upgrade this very blog to use `v3` and I am very happy with the results. While there are new features, I didn't really used them until now. Anyway, let's highlight a few things here.

### Executable!
If you want to play around, you can easily install the template:
```bash
dotnet new install xunit.v3.templates
```

And create a new project:
```bash
dotnet new xunit3
```

The first thing you will notice is something like this inside the created csproj:
```xml
<OutputType>Exe</OutputType>
```

Yes, the test project is now an executable! So test runner and test project are the same thing! So you can run your tests like this:
```bash
dotnet run
```

For example the unit tests of this blog post will print something like this:
```no-class
$ dotnet run
xUnit.net v3 In-Process Runner v0.4.0-pre.20+7ef620d780 (64-bit .NET 9.0.0-rc.1.24431.7)
  Discovering: LinkDotNet.Blog.UnitTests
  Discovered:  LinkDotNet.Blog.UnitTests
  Starting:    LinkDotNet.Blog.UnitTests
  Finished:    LinkDotNet.Blog.UnitTests
=== TEST EXECUTION SUMMARY ===
   LinkDotNet.Blog.UnitTests  Total: 246, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 0.350s
```

You can also print out which test-classes, traits and what not you have via the command line:
```bash
dotnet run -- -list classes
```

The extra `--` between `run` and `-list` is needed to pass the arguments to the test runner and not the `dotnet` cli itself.
In theory you can also create a self-contained executable and run it on a different machine without the need of the `dotnet` cli at all! Hello **TUnit**! 

### Migration
The migration (at least for me) was straight forward. Instead of the `xunit` package reference you have to use `xunit.v3` - they wanted to make it very explicit! And as discussed earlier, I switched from library (which is the default if you don't set any `OutputType`) to executable. But as of now, you don't have to do this. You can still use the library output type - but there isn't really a reason to do so.

A very detailed migration guide can be found [here](https://xunit.net/docs/getting-started/v3/migration).

### Cancellation Tokens
A pretty neat feature is the support for cancellation tokens that are passed to the test as `TestContext.Current.CancellationToken`:

```csharp
await Task.Delay(1000, TestContext.Current.CancellationToken);
```

This makes it more convenient to cancel async things if you have to! The analyzers will automatically "inform" you if you are not using the token.

### Matrix theory data

Also hello **TUnit**! Version 3 brings also support for defining theory data in a matrix style. This is a feature that is also available in **TUnit** and it is very handy if you have a lot of data to test. Here is an example (shamelessly stolen from [*"What's New*"](https://xunit.net/docs/getting-started/v3/whats-new#whats-new-in-the-core-framework)):

```csharp
public static TheoryData<int, string> MyData =
    new MatrixTheoryData<int, string>(
        [42, 2112, 2600],
        ["Hello", "World"]
    );
```

Which will result in 6 tests:
 * 42, "Hello"
 * 42, "World"
 * 2112, "Hello"
 * 2112, "World"
 * 2600, "Hello"
 * 2600, "World"

### `IAsyncLifetime`
A small quality of life "fix" is that `IAsyncLifetime` directly inherits from `IAsyncDisposable`. This makes it easier to implement the interface and you don't have to implement `DisposeAsync` anymore "twice". If you only need to dispose of things, `IAsyncDisposable` is enough! You don't have to implement `IAsyncLifetime` anymore. This is only useful, if you need an `InitializeAsync` call!

## Conclusion and will I switch to TUnit?
I am very happy with the new features of `v3` and I will definitely migrate more of my projects to it. Will I switch to **TUnit**? For now: No. And the main reason is simple: I love the simplicity and to a big chunk the ideology behind xUnit. Look at the following test in **TUnit**:

```csharp
[DependsOn(nameof(Test1), [typeof(string), typeof(int)])]
public void Test2() { ... }
```

`DependsOn` is an explicit feature. I don't like that tests depend on other tests. I do see some value - for example if you have Playwright e2e tests where it is expensive to setup everything for every test case. But besides that, I see a a **BIG** area of misuse. Of course I cherry-picked this example, and there are many things that are very nice! Let's see where this is headed and maybe in a few months I will switch to **TUnit**.