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
<PackageReference Include="TUnit.Engine" Version="1.61.0" />
<PackageVersion Include="TUnit.Engine" Version="1.61.0" />
<PackageReference Include="TUnit.Engine" />
paket add TUnit.Engine --version 1.61.0
#r "nuget: TUnit.Engine, 1.61.0"
#:package TUnit.Engine@1.61.0
#addin nuget:?package=TUnit.Engine&version=1.61.0
#tool nuget:?package=TUnit.Engine&version=1.61.0
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">
</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
Using the Project Template (Recommended)
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 packagestests/— unit, integration, snapshot, fixture, and cross-language test projectsbenchmarks/— BenchmarkDotNet and profiling workloadsexamples/— sample applications and test projectstools/— repository automation and development utilitieseng/— shared MSBuild imports, signing key, and engineering assetsscripts/— local build, test, and profiling entry points
Solutions and repository-wide SDK, package, and build configuration remain at root.
Community
- Documentation — guides, tutorials, and API reference
- GitHub Discussions — questions and ideas welcome
- Issues — bug reports and feature requests
- Changelog
| Product | Versions 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. |
-
.NETStandard 2.0
- EnumerableAsyncProcessor (>= 3.8.4)
- Microsoft.Testing.Extensions.TrxReport.Abstractions (>= 2.3.2)
- Microsoft.Testing.Platform (>= 2.3.2)
- Microsoft.Testing.Platform.MSBuild (>= 2.3.2)
- System.Threading.Channels (>= 9.0.0)
- System.Threading.Tasks.Extensions (>= 4.6.3)
- TUnit.Core (>= 1.61.0)
-
net10.0
- EnumerableAsyncProcessor (>= 3.8.4)
- Microsoft.Testing.Extensions.TrxReport.Abstractions (>= 2.3.2)
- Microsoft.Testing.Platform (>= 2.3.2)
- Microsoft.Testing.Platform.MSBuild (>= 2.3.2)
- TUnit.Core (>= 1.61.0)
-
net8.0
- EnumerableAsyncProcessor (>= 3.8.4)
- Microsoft.Testing.Extensions.TrxReport.Abstractions (>= 2.3.2)
- Microsoft.Testing.Platform (>= 2.3.2)
- Microsoft.Testing.Platform.MSBuild (>= 2.3.2)
- System.Threading.Channels (>= 9.0.0)
- TUnit.Core (>= 1.61.0)
-
net9.0
- EnumerableAsyncProcessor (>= 3.8.4)
- Microsoft.Testing.Extensions.TrxReport.Abstractions (>= 2.3.2)
- Microsoft.Testing.Platform (>= 2.3.2)
- Microsoft.Testing.Platform.MSBuild (>= 2.3.2)
- TUnit.Core (>= 1.61.0)
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 |