TUnit.Engine 1.61.0

Prefix Reserved
dotnet add package TUnit.Engine --version 1.61.0
                    
NuGet\Install-Package TUnit.Engine -Version 1.61.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TUnit.Engine" Version="1.61.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TUnit.Engine" Version="1.61.0" />
                    
Directory.Packages.props
<PackageReference Include="TUnit.Engine" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TUnit.Engine --version 1.61.0
                    
#r "nuget: TUnit.Engine, 1.61.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TUnit.Engine@1.61.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TUnit.Engine&version=1.61.0
                    
Install as a Cake Addin
#tool nuget:?package=TUnit.Engine&version=1.61.0
                    
Install as a Cake Tool

TUnit

TUnit

A modern .NET testing framework. Tests are discovered at compile time via source generators, run in parallel by default, and work under Native AOT — all built on Microsoft.Testing.Platform.

<div align="center">

thomhurst%2FTUnit | Trendshift

GitHub Repo stars GitHub Issues or Pull Requests GitHub Sponsors nuget NuGet Downloads GitHub Workflow Status (with event) GitHub last commit (branch) License

</div>

What it looks like

[Test]
[Arguments("GOLD", 100.00, 80.00)]
[Arguments("SILVER", 100.00, 90.00)]
public async Task Discount_Is_Applied(string tier, double subtotal, double expected)
{
    var checkout = new CheckoutService();

    var total = await checkout.ApplyDiscountAsync(tier, subtotal);

    await Assert.That(total).IsEqualTo(expected);
}

When a test fails, TUnit tells you what happened — including the actual expression you wrote:

Expected to be 80
but found 100

at Assert.That(total).IsEqualTo(expected)

Comparing objects? Instead of dumping two object graphs at you, TUnit pinpoints the difference:

Expected to be equal to Employee { FirstName = "Victoria", LastName = "Apanii", Age = 30 }
but differs at member FirstName: expected "Victoria" but found "ictoria"

at Assert.That(actualEmployee).IsEqualTo(expectedEmployee)

Why TUnit?

  • Compile-time test discovery — tests are wired up by a source generator at build time, not found via reflection at runtime. Faster startup, better IDE integration, and full Native AOT / trimming support.
  • Compile-time safety — a suite of Roslyn analyzers ships in the box, so mistakes like invalid hook signatures, broken data sources, and misused assertions fail your build, not your CI run.
  • Parallel by default, with real control — tests run concurrently out of the box; [DependsOn], [NotInParallel], and [ParallelLimiter<T>] give you precise ordering and throttling when you need it.
  • Batteries included — rich async assertions, shared fixtures with dependency injection, lifecycle hooks at every scope, and a source-generated mocking library — with first-class integrations for ASP.NET Core, Aspire, and Playwright.

Performance

Source generation shifts work from run time to build time: you pay a little up front at build, and every test run after that starts faster — dramatically so under Native AOT. The same test suites, run on every framework:

Scenario TUnit (AOT) TUnit xUnit v3 NUnit MSTest
Data-driven tests 16.65 ms 265.70 ms 523.90 ms 523.01 ms 529.13 ms
Async-heavy tests 113.1 ms 360.0 ms 605.6 ms 608.6 ms 676.0 ms
Matrix combinations 119.6 ms 474.1 ms 1,469.2 ms 1,468.9 ms 1,515.3 ms
Large suites (scale) 29.38 ms 263.55 ms 504.14 ms 488.30 ms 495.94 ms
Massive parallelism 217.6 ms 467.8 ms 2,945.7 ms 1,107.5 ms 2,993.9 ms
Setup/teardown lifecycle 335.8 ms 1,050.2 ms 1,008.8 ms 1,099.9 ms

<sub>Mean wall-clock time to run the same test suite. TUnit (AOT) 1.58.0 · TUnit 1.58.0 · xUnit v3 3.2.2 · NUnit 4.6.1 · MSTest 4.3.0. .NET SDK 10.0.301, .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v4. Updated 2026-07-12 — regenerated weekly by the Speed Comparison workflow. Full results and methodology: tunit.dev/docs/benchmarks.</sub>

Getting Started

dotnet new install TUnit.Templates
dotnet new TUnit -n "MyTestProject"
cd MyTestProject
dotnet run

Manual Installation

dotnet add package TUnit

Getting Started Guide · Migration Guides

A tour of the good parts

Data-driven tests

[Test]
[Arguments("user1@test.com", "ValidPassword123")]
[Arguments("admin@test.com", "AdminPass789")]
public async Task User_Login_Succeeds(string email, string password) { ... }

// Matrix — generates a test for every combination (9 total here)
[Test]
[MatrixDataSource]
public async Task Database_Operations_Work(
    [Matrix("Create", "Update", "Delete")] string operation,
    [Matrix("User", "Product", "Order")] string entity) { ... }

Need more? [MethodDataSource] pulls rows from a method, and custom DataSourceGenerator<T> attributes let you build your own sources.

Assertions that explain themselves

Assertions are async, chainable, and produce the focused failure messages shown above:

await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK)
    .Because("the health endpoint should always be up");

await Assert.That(order.Items)
    .Count().IsEqualTo(3)
    .And.Contains(item => item.Sku == "ABC-123");

Defining your own assertion is one attribute on a plain method — TUnit generates the fluent extension for you:

[GenerateAssertion]
public static bool IsPositive(this int value) => value > 0;

// Now available on Assert.That:
await Assert.That(account.Balance).IsPositive();

Shared fixtures without the ceremony

Inject anything into your test classes with [ClassDataSource<T>]. Implement IAsyncInitializer for async setup, IAsyncDisposable for teardown, and pick a sharing scope — None, PerClass, PerAssembly, PerTestSession, or Keyed:

public class PostgresContainer : IAsyncInitializer, IAsyncDisposable
{
    public Task InitializeAsync() { /* start container */ }
    public ValueTask DisposeAsync() { /* stop container */ }
}

public class OrderRepositoryTests
{
    [ClassDataSource<PostgresContainer>(Shared = SharedType.PerTestSession)]
    public required PostgresContainer Postgres { get; init; }

    [Test]
    public async Task Saves_Order() { /* Postgres is initialized and shared across the whole run */ }
}

Property injection keeps base test classes clean — subclasses inherit the fixture without re-threading constructor parameters. Prefer a constructor param? That works too. Disposal is reference-counted, so shared fixtures are torn down exactly when the last test using them finishes.

Parallelism you control

Everything runs in parallel by default. Opt out or sequence tests where it matters:

[Test]
public async Task Register_User() { ... }

[Test, DependsOn(nameof(Register_User))]
[Retry(3)]
public async Task Login_With_Registered_User() { ... } // runs after Register_User passes

[Test, NotInParallel("checkout-db")] // tests sharing a key never overlap
public async Task Migrates_Schema() { ... }

[Repeat(n)], [Timeout(ms)], and [ParallelLimiter<T>] round out the set.

Lifecycle hooks at every scope

[Before(Test)]        // also: Class, Assembly, TestSession
public async Task SetUp() { ... }

[After(Class)]
public static async Task TearDownDatabase(ClassHookContext context) { ... }

Mocking built in

TUnit.Mocks is a source-generated, Native AOT-compatible mocking library — no runtime proxies, no Castle.Core. It works with any test framework:

var gateway = IPaymentGateway.Mock();   // or Mock.Of<IPaymentGateway>()

gateway.ChargeAsync(Any<decimal>()).Returns(new ChargeResult(Success: true));

var checkout = new CheckoutService(gateway.Object);
await checkout.CompleteAsync(cart);

gateway.ChargeAsync(99.99m).WasCalled(Times.Once);

Companion packages mock the annoying stuff for you:

// TUnit.Mocks.Http — a real HttpClient backed by a scriptable handler
using var client = Mock.HttpClient("https://api.example.com");
client.Handler.OnGet("/users/1").RespondWithJson("""{ "id": 1 }""");

// TUnit.Mocks.Logging — capture and verify ILogger output
var logger = Mock.Logger<CheckoutService>();
logger.VerifyLog().AtLevel(LogLevel.Warning).ContainingMessage("retrying").WasCalled(Times.Once);

Custom attributes

Extend built-in base classes to create your own skip conditions, retry logic, and more:

public class WindowsOnlyAttribute : SkipAttribute
{
    public WindowsOnlyAttribute() : base("Windows only") { }

    public override Task<bool> ShouldSkip(TestContext testContext)
        => Task.FromResult(!OperatingSystem.IsWindows());
}

[Test, WindowsOnly]
public async Task Windows_Specific_Feature() { ... }

Integrations

ASP.NET Core

public class ApiFactory : TestWebApplicationFactory<Program>;

[ClassDataSource<ApiFactory>(Shared = SharedType.PerTestSession)]
public class HealthCheckTests(ApiFactory factory)
{
    [Test]
    public async Task Health_Endpoint_Responds()
    {
        using var client = factory.CreateClient();
        var response = await client.GetAsync("/health");

        await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
    }
}

Aspire

Spin up your whole distributed app once per test session, with resource log forwarding and OpenTelemetry capture built in:

public class AppFixture : AspireFixture<Projects.MyApp_AppHost>;

[ClassDataSource<AppFixture>(Shared = SharedType.PerTestSession)]
public class ApiServiceTests(AppFixture app)
{
    [Test]
    public async Task Api_Returns_Data()
    {
        var client = app.CreateHttpClient("apiservice");
        var response = await client.GetAsync("/weather");

        await Assert.That(response.IsSuccessStatusCode).IsTrue();
    }
}

Playwright

Inherit from PageTest and a browser page is waiting for you — lifecycle fully managed:

public class HomePageTests : PageTest
{
    [Test]
    public async Task Homepage_Loads()
    {
        await Page.GotoAsync("https://example.com");

        await Assert.That(await Page.TitleAsync()).Contains("Example");
    }
}

Property-based testing (FsCheck)

[Test, FsCheckProperty]
public bool Reversing_Twice_Returns_Original(int[] array) =>
    array.SequenceEqual(array.AsEnumerable().Reverse().Reverse());

More than C#

TUnit runs F# and VB.NET test projects too, and TUnit.Assertions.FSharp provides idiomatic F# assertion helpers.

IDE Support

IDE Notes
Visual Studio 2022 (17.13+) Works out of the box
Visual Studio 2022 (earlier) Enable "Use testing platform server mode" in Tools > Manage Preview Features
JetBrains Rider Enable "Testing Platform support" in Settings > Build, Execution, Deployment > Unit Testing > Testing Platform
VS Code Install C# Dev Kit and enable "Use Testing Platform Protocol"
CLI Works with dotnet test, dotnet run, and direct execution

Packages

Package Purpose
TUnit Start here — the full framework (Core + Engine + Assertions)
TUnit.Core Shared test library components without an execution engine
TUnit.Engine Execution engine for test projects
TUnit.Assertions Standalone assertions — works with other test frameworks too
TUnit.Assertions.Should Optional FluentAssertions-style value.Should().BeEqualTo(...) syntax over TUnit.Assertions (beta)
TUnit.Mocks Source-generated, AOT-compatible mocking — works with any test runner
TUnit.Mocks.Http HttpClient mocking helpers built on TUnit.Mocks
TUnit.Mocks.Logging ILogger capture/verification helpers built on TUnit.Mocks
TUnit.AspNetCore ASP.NET Core integration — WebApplicationFactory-based test fixtures
TUnit.Aspire Aspire integration — distributed app host fixtures with OpenTelemetry capture
TUnit.Playwright Playwright integration with automatic browser lifecycle management
TUnit.FsCheck Property-based testing via FsCheck
TUnit.OpenTelemetry OpenTelemetry instrumentation for test runs

Migrating from xUnit, NUnit, or MSTest?

The syntax will feel familiar. For example, xUnit's [Fact] becomes [Test], and [Theory] + [InlineData] becomes [Test] + [Arguments]. See the migration guides for full details: xUnit · NUnit · MSTest.

Repository layout

  • src/ — product libraries, analyzers, source generators, integrations, templates, and tools shipped as packages
  • tests/ — unit, integration, snapshot, fixture, and cross-language test projects
  • benchmarks/ — BenchmarkDotNet and profiling workloads
  • examples/ — sample applications and test projects
  • tools/ — repository automation and development utilities
  • eng/ — shared MSBuild imports, signing key, and engineering assets
  • scripts/ — local build, test, and profiling entry points

Solutions and repository-wide SDK, package, and build configuration remain at root.

Community

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on TUnit.Engine:

Package Downloads
TUnit

A .NET Testing Framework

GitHub repositories (5)

Showing the top 5 popular GitHub repositories that depend on TUnit.Engine:

Repository Stars
eiriktsarpalis/PolyType
Practical generic programming for .NET
MatterHackers/agg-sharp
A C# port of Anti-Grain Geometry (AGG) with extensions for common scenarios
AArnott/Nerdbank.MessagePack
A feature-packed .NET MessagePack serialization library with great performance and simplicity. msgpack.io[C#]
viceroypenguin/RBush
R-Tree Implementation for C#
steamcore/TinyIpc
.NET inter process broadcast message bus with supporting classes
Version Downloads Last Updated
1.61.0 0 7/16/2026
1.60.0 18,750 7/15/2026
1.59.0 23,117 7/13/2026
1.58.0 101,845 7/1/2026
1.57.17 1,596 7/1/2026
1.57.14 7,357 6/30/2026
1.57.0 19,433 6/28/2026
1.56.35 31,396 6/24/2026
1.56.25 22,266 6/22/2026
1.56.18 24,147 6/19/2026
1.56.0 75,058 6/15/2026
1.55.2 13,508 6/14/2026
1.55.0 2,115 6/14/2026
1.54.0 12,540 6/12/2026
1.53.0 57,467 6/8/2026
1.51.0 14,452 6/7/2026
1.50.0 22,946 6/5/2026
1.49.0 15,826 6/3/2026
1.48.6 79,735 6/1/2026
1.48.0 4,839 5/31/2026
Loading failed