๐ The Modern Testing Framework for .NET
TUnit is a next-generation testing framework for C# that outpaces traditional frameworks with source-generated tests, parallel execution by default, and Native AOT support. Built on the modern Microsoft.Testing.Platform, TUnit delivers faster test runs, better developer experience, and unmatched flexibility.
โก Why Choose TUnit?
Feature | Traditional Frameworks | TUnit |
---|---|---|
Test Discovery | โ Runtime reflection | โ Compile-time generation |
Execution Speed | โ Sequential by default | โ Parallel by default |
Modern .NET | โ ๏ธ Limited AOT support | โ Full Native AOT & trimming |
Test Dependencies | โ Not supported | โ
[DependsOn] chains |
Resource Management | โ Manual lifecycle | โ Intelligent cleanup |
โก Parallel by Default - Tests run concurrently with intelligent dependency management
๐ฏ Compile-Time Discovery - Know your test structure before runtime
๐ง Modern .NET Ready - Native AOT, trimming, and latest .NET features
๐ญ Extensible - Customize data sources, attributes, and test behavior
๐ Complete Documentation & Learning Center
๐ New to TUnit? Start with our Getting Started Guide
๐ Migrating? See our Migration Guides
๐ฏ Advanced Features? Explore Data-Driven Testing, Test Dependencies, and Parallelism Control
๐ Quick Start
Using the Project Template (Recommended)
dotnet new install TUnit.Templates
dotnet new TUnit -n "MyTestProject"
Manual Installation
dotnet add package TUnit --prerelease
๐ ๐ Complete Documentation & Guides - Everything you need to master TUnit
โจ Key Features
๐ Performance & Modern Platform
|
๐ฏ Advanced Test Control
|
๐ Rich Data & Assertions
|
๐ง Developer Experience
|
๐ Simple Test Example
[Test]
public async Task User_Creation_Should_Set_Timestamp()
{
// Arrange
var userService = new UserService();
// Act
var user = await userService.CreateUserAsync("john.doe@example.com");
// Assert - TUnit's fluent assertions
await Assert.That(user.CreatedAt)
.IsEqualTo(DateTime.Now)
.Within(TimeSpan.FromMinutes(1));
await Assert.That(user.Email)
.IsEqualTo("john.doe@example.com");
}
๐ฏ Data-Driven Testing
[Test]
[Arguments("user1@test.com", "ValidPassword123")]
[Arguments("user2@test.com", "AnotherPassword456")]
[Arguments("admin@test.com", "AdminPass789")]
public async Task User_Login_Should_Succeed(string email, string password)
{
var result = await authService.LoginAsync(email, password);
await Assert.That(result.IsSuccess).IsTrue();
}
// Matrix testing - tests all combinations
[Test]
[MatrixDataSource]
public async Task Database_Operations_Work(
[Matrix("Create", "Update", "Delete")] string operation,
[Matrix("User", "Product", "Order")] string entity)
{
await Assert.That(await ExecuteOperation(operation, entity))
.IsTrue();
}
๐ Advanced Test Orchestration
[Before(Class)]
public static async Task SetupDatabase(ClassHookContext context)
{
await DatabaseHelper.InitializeAsync();
}
[Test, DisplayName("Register a new account")]
[MethodDataSource(nameof(GetTestUsers))]
public async Task Register_User(string username, string password)
{
// Test implementation
}
[Test, DependsOn(nameof(Register_User))]
[Retry(3)] // Retry on failure
public async Task Login_With_Registered_User(string username, string password)
{
// This test runs after Register_User completes
}
[Test]
[ParallelLimit<LoadTestParallelLimit>] // Custom parallel control
[Repeat(100)] // Run 100 times
public async Task Load_Test_Homepage()
{
// Performance testing
}
// Custom attributes
[Test, WindowsOnly, RetryOnHttpError(5)]
public async Task Windows_Specific_Feature()
{
// Platform-specific test with custom retry logic
}
public class LoadTestParallelLimit : IParallelLimit
{
public int Limit => 10; // Limit to 10 concurrent executions
}
๐ง Smart Test Control
// Custom conditional execution
public class WindowsOnlyAttribute : SkipAttribute
{
public WindowsOnlyAttribute() : base("Windows only test") { }
public override Task<bool> ShouldSkip(TestContext testContext)
=> Task.FromResult(!OperatingSystem.IsWindows());
}
// Custom retry logic
public class RetryOnHttpErrorAttribute : RetryAttribute
{
public RetryOnHttpErrorAttribute(int times) : base(times) { }
public override Task<bool> ShouldRetry(TestInformation testInformation,
Exception exception, int currentRetryCount)
=> Task.FromResult(exception is HttpRequestException { StatusCode: HttpStatusCode.ServiceUnavailable });
}
๐ฏ Perfect For Every Testing Scenario
๐งช Unit Testing
Fast, isolated, and reliable |
๐ Integration Testing
Stateful workflows made simple |
โก Load Testing
Built-in performance testing |
๐ What Makes TUnit Different?
Compile-Time Intelligence
Tests are discovered at build time, not runtime - enabling faster discovery, better IDE integration, and precise resource lifecycle management.
Parallel-First Architecture
Built for concurrency from day one with [DependsOn]
for test chains, [ParallelLimit]
for resource control, and intelligent scheduling.
Extensible by Design
The DataSourceGenerator<T>
pattern and custom attribute system let you extend TUnit's capabilities without modifying core framework code.
๐ Community & Ecosystem
๐ค Active Community
- ๐ Official Documentation - Comprehensive guides, tutorials, and API reference
- ๐ฌ GitHub Discussions - Get help and share ideas
- ๐ Issue Tracking - Report bugs and request features
- ๐ข Release Notes - Stay updated with latest improvements
๐ ๏ธ IDE Support
TUnit works seamlessly across all major .NET development environments:
Visual Studio (2022 17.13+)
โ Fully supported - No additional configuration needed for latest versions
โ๏ธ Earlier versions: Enable "Use testing platform server mode" in Tools > Manage Preview Features
JetBrains Rider
โ Fully supported
โ๏ธ Setup: Enable "Testing Platform support" in Settings > Build, Execution, Deployment > Unit Testing > VSTest
Visual Studio Code
โ Fully supported
โ๏ธ Setup: Install C# Dev Kit and enable "Use Testing Platform Protocol"
Command Line
โ
Full CLI support - Works with dotnet test
, dotnet run
, and direct executable execution
๐ฆ Package Options
Package | Use Case |
---|---|
TUnit |
โญ Start here - Complete testing framework (includes Core + Engine + Assertions) |
TUnit.Core |
๐ Test libraries and shared components (no execution engine) |
TUnit.Engine |
๐ Test execution engine and adapter (for test projects) |
TUnit.Assertions |
โ Standalone assertions (works with any test framework) |
TUnit.Playwright |
๐ญ Playwright integration with automatic lifecycle management |
๐ฏ Migration from Other Frameworks
Coming from NUnit or xUnit? TUnit maintains familiar syntax while adding modern capabilities:
// Enhanced with TUnit's advanced features
[Test]
[Arguments("value1")]
[Arguments("value2")]
[Retry(3)]
[ParallelLimit<CustomLimit>]
public async Task Modern_TUnit_Test(string value) { }
๐ Need help migrating? Check our detailed Migration Guides with step-by-step instructions for xUnit, NUnit, and MSTest.
๐ก Current Status
The API is mostly stable, but may have some changes based on feedback or issues before v1.0 release.
๐ Ready to Experience the Future of .NET Testing?
โก Start in 30 Seconds
# Create a new test project with examples
dotnet new install TUnit.Templates && dotnet new TUnit -n "MyAwesomeTests"
# Or add to existing project
dotnet add package TUnit --prerelease
๐ฏ Why Wait? Join the Movement
๐ PerformanceOptimized execution Parallel by default Zero reflection overhead |
๐ฎ Future-ReadyNative AOT support Latest .NET features Source generation |
๐ ๏ธ Developer ExperienceCompile-time checks Rich IDE integration Intelligent debugging |
๐ญ FlexibilityTest dependencies Custom attributes Extensible architecture |
๐ Learn More: tunit.dev | ๐ฌ Get Help: GitHub Discussions | โญ Show Support: Star on GitHub
TUnit is actively developed and production-ready. Join our growing community of developers who've made the switch!
Performance Benchmark
Scenario: Building the test project
macos-latest
BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]
Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev | Median |
---|---|---|---|---|
Build_TUnit | 1,197.4 ms | 87.87 ms | 249.28 ms | 1,105.6 ms |
Build_NUnit | 799.6 ms | 9.50 ms | 7.93 ms | 798.9 ms |
Build_xUnit | 781.1 ms | 13.08 ms | 10.93 ms | 778.8 ms |
Build_MSTest | 850.8 ms | 16.72 ms | 26.52 ms | 841.9 ms |
ubuntu-latest
BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
Build_TUnit | 1.969 s | 0.0388 s | 0.0658 s |
Build_NUnit | 1.495 s | 0.0269 s | 0.0238 s |
Build_xUnit | 1.493 s | 0.0242 s | 0.0226 s |
Build_MSTest | 1.513 s | 0.0177 s | 0.0166 s |
windows-latest
BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)
AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
Build_TUnit | 1.955 s | 0.0386 s | 0.0589 s |
Build_NUnit | 1.563 s | 0.0294 s | 0.0275 s |
Build_xUnit | 1.517 s | 0.0287 s | 0.0319 s |
Build_MSTest | 1.541 s | 0.0248 s | 0.0232 s |
Scenario: A single test that completes instantly (including spawning a new process and initialising the test framework)
macos-latest
BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]
Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
TUnit_AOT | 75.22 ms | 0.404 ms | 0.358 ms |
TUnit | 469.34 ms | 8.643 ms | 9.953 ms |
NUnit | 706.44 ms | 11.712 ms | 10.383 ms |
xUnit | 734.35 ms | 14.193 ms | 18.454 ms |
MSTest | 619.72 ms | 10.187 ms | 9.529 ms |
ubuntu-latest
BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
TUnit_AOT | 27.28 ms | 0.739 ms | 2.178 ms |
TUnit | 826.51 ms | 16.185 ms | 16.621 ms |
NUnit | 1,288.58 ms | 16.823 ms | 15.736 ms |
xUnit | 1,335.37 ms | 9.836 ms | 9.201 ms |
MSTest | 1,130.57 ms | 14.008 ms | 13.104 ms |
windows-latest
BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)
AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
TUnit_AOT | 58.46 ms | 1.789 ms | 5.219 ms |
TUnit | 895.06 ms | 17.378 ms | 26.539 ms |
NUnit | 1,359.08 ms | 26.245 ms | 25.777 ms |
xUnit | 1,382.43 ms | 13.794 ms | 12.903 ms |
MSTest | 1,176.92 ms | 11.738 ms | 9.802 ms |
Scenario: A test that takes 50ms to execute, repeated 100 times (including spawning a new process and initialising the test framework)
macos-latest
BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]
Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
TUnit_AOT | 243.3 ms | 11.49 ms | 33.70 ms |
TUnit | 634.9 ms | 20.22 ms | 59.60 ms |
NUnit | 13,981.2 ms | 279.41 ms | 583.22 ms |
xUnit | NA | NA | NA |
MSTest | 14,256.3 ms | 282.60 ms | 643.62 ms |
Benchmarks with issues: RuntimeBenchmarks.xUnit: .NET 9.0(Runtime=.NET 9.0)
ubuntu-latest
BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
TUnit_AOT | 74.51 ms | 0.570 ms | 0.476 ms |
TUnit | 907.93 ms | 17.675 ms | 25.349 ms |
NUnit | 6,290.38 ms | 15.471 ms | 14.471 ms |
xUnit | 6,429.98 ms | 20.035 ms | 18.740 ms |
MSTest | 6,246.55 ms | 17.250 ms | 16.136 ms |
windows-latest
BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)
AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 9.0.301
[Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
.NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
Job=.NET 9.0 Runtime=.NET 9.0
Method | Mean | Error | StdDev |
---|---|---|---|
TUnit_AOT | 110.5 ms | 1.84 ms | 1.97 ms |
TUnit | 985.9 ms | 19.53 ms | 26.73 ms |
NUnit | 7,531.4 ms | 41.54 ms | 34.69 ms |
xUnit | 7,585.3 ms | 18.56 ms | 17.36 ms |
MSTest | 7,467.0 ms | 22.65 ms | 21.19 ms |