TUnit.Core
1.61.0
Prefix Reserved
dotnet add package TUnit.Core --version 1.61.0
NuGet\Install-Package TUnit.Core -Version 1.61.0
<PackageReference Include="TUnit.Core" Version="1.61.0" />
<PackageVersion Include="TUnit.Core" Version="1.61.0" />
<PackageReference Include="TUnit.Core" />
paket add TUnit.Core --version 1.61.0
#r "nuget: TUnit.Core, 1.61.0"
#:package TUnit.Core@1.61.0
#addin nuget:?package=TUnit.Core&version=1.61.0
#tool nuget:?package=TUnit.Core&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
- Microsoft.CSharp (>= 4.7.0)
- System.Collections.Immutable (>= 9.0.0)
- System.Text.Json (>= 9.0.0)
- System.Threading.Tasks.Extensions (>= 4.6.3)
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (105)
Showing the top 5 NuGet packages that depend on TUnit.Core:
| Package | Downloads |
|---|---|
|
TUnit.Engine
A .NET Testing Framework |
|
|
Verify.TUnit
Enables verification of complex models and documents. |
|
|
Soenneker.Serilog.Sinks.TUnit
A Serilog sink for logging within TUnit |
|
|
TUnit.Logging.Microsoft
A .NET Testing Framework |
|
|
TUnit.OpenTelemetry
Auto-wires an OpenTelemetry TracerProvider for TUnit test processes. Install to get distributed tracing with zero configuration. |
GitHub repositories (8)
Showing the top 8 popular GitHub repositories that depend on TUnit.Core:
| Repository | Stars |
|---|---|
|
reactiveui/ReactiveUI
An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
|
|
|
VerifyTests/Verify
Verify is a snapshot testing tool that simplifies the assertion of complex data models and documents.
|
|
|
wiremock/WireMock.Net
WireMock.Net is a flexible product for stubbing and mocking web HTTP responses using advanced request matching and response templating. Based on WireMock Java, but extended and different functionality. Full documentation can be found at https://wiremock.org/dotnet/.
|
|
|
reqnroll/Reqnroll
Open-source Cucumber-style BDD test automation framework for .NET.
|
|
|
thomhurst/ModularPipelines
Write your pipelines in C# !
|
|
|
Eventuous/eventuous
Event Sourcing library for .NET
|
|
|
SwissLife-OSS/snapshooter
Snapshooter is a snapshot testing tool for .NET Core and .NET Framework
|
|
|
DamianMorozov/OpenTgResearcher
OpenTgResearcher - tool for analyzing Telegram chats and downloading their content
|
| Version | Downloads | Last Updated |
|---|---|---|
| 1.61.0 | 0 | 7/16/2026 |
| 1.60.0 | 19,258 | 7/15/2026 |
| 1.59.0 | 33,423 | 7/13/2026 |
| 1.58.0 | 105,847 | 7/1/2026 |
| 1.57.17 | 1,831 | 7/1/2026 |
| 1.57.14 | 7,664 | 6/30/2026 |
| 1.57.0 | 25,896 | 6/28/2026 |
| 1.56.35 | 33,576 | 6/24/2026 |
| 1.56.25 | 25,006 | 6/22/2026 |
| 1.56.18 | 27,818 | 6/19/2026 |
| 1.56.0 | 123,705 | 6/15/2026 |
| 1.55.2 | 21,767 | 6/14/2026 |
| 1.55.0 | 2,425 | 6/14/2026 |
| 1.54.0 | 16,892 | 6/12/2026 |
| 1.53.0 | 67,065 | 6/8/2026 |
| 1.51.0 | 21,922 | 6/7/2026 |
| 1.50.0 | 26,385 | 6/5/2026 |
| 1.49.0 | 15,970 | 6/3/2026 |
| 1.48.6 | 96,237 | 6/1/2026 |
| 1.48.0 | 12,798 | 5/31/2026 |