A modern, fast and flexible .NET testing framework
Key metrics and engagement data
Repository has been active for 1 year, 5 months
⭐400
Want deeper insights? Explore GitObs.com
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.
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
🚀 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
bash1dotnet new install TUnit.Templates2dotnet new TUnit -n "MyTestProject"
bash1dotnet add package TUnit --prerelease
📖 📚 Complete Documentation & Guides - Everything you need to master TUnit
🚀 Performance & Modern Platform
|
🎯 Advanced Test Control
|
📊 Rich Data & Assertions
|
🔧 Developer Experience
|
csharp1[Test]2public async Task User_Creation_Should_Set_Timestamp()3{4 // Arrange5 var userService = new UserService();67 // Act8 var user = await userService.CreateUserAsync("[email protected]");910 // Assert - TUnit's fluent assertions11 await Assert.That(user.CreatedAt)12 .IsEqualTo(DateTime.Now)13 .Within(TimeSpan.FromMinutes(1));1415 await Assert.That(user.Email)16 .IsEqualTo("[email protected]");17}
csharp1[Test]2[Arguments("[email protected]", "ValidPassword123")]3[Arguments("[email protected]", "AnotherPassword456")]4[Arguments("[email protected]", "AdminPass789")]5public async Task User_Login_Should_Succeed(string email, string password)6{7 var result = await authService.LoginAsync(email, password);8 await Assert.That(result.IsSuccess).IsTrue();9}1011// Matrix testing - tests all combinations12[Test]13[MatrixDataSource]14public async Task Database_Operations_Work(15 [Matrix("Create", "Update", "Delete")] string operation,16 [Matrix("User", "Product", "Order")] string entity)17{18 await Assert.That(await ExecuteOperation(operation, entity))19 .IsTrue();20}
csharp1[Before(Class)]2public static async Task SetupDatabase(ClassHookContext context)3{4 await DatabaseHelper.InitializeAsync();5}67[Test, DisplayName("Register a new account")]8[MethodDataSource(nameof(GetTestUsers))]9public async Task Register_User(string username, string password)10{11 // Test implementation12}1314[Test, DependsOn(nameof(Register_User))]15[Retry(3)] // Retry on failure16public async Task Login_With_Registered_User(string username, string password)17{18 // This test runs after Register_User completes19}2021[Test]22[ParallelLimit<LoadTestParallelLimit>] // Custom parallel control23[Repeat(100)] // Run 100 times24public async Task Load_Test_Homepage()25{26 // Performance testing27}2829// Custom attributes30[Test, WindowsOnly, RetryOnHttpError(5)]31public async Task Windows_Specific_Feature()32{33 // Platform-specific test with custom retry logic34}3536public class LoadTestParallelLimit : IParallelLimit37{38 public int Limit => 10; // Limit to 10 concurrent executions39}
csharp1// Custom conditional execution2public class WindowsOnlyAttribute : SkipAttribute3{4 public WindowsOnlyAttribute() : base("Windows only test") { }56 public override Task<bool> ShouldSkip(TestContext testContext)7 => Task.FromResult(!OperatingSystem.IsWindows());8}910// Custom retry logic11public class RetryOnHttpErrorAttribute : RetryAttribute12{13 public RetryOnHttpErrorAttribute(int times) : base(times) { }1415 public override Task<bool> ShouldRetry(TestInformation testInformation,16 Exception exception, int currentRetryCount)17 => Task.FromResult(exception is HttpRequestException { StatusCode: HttpStatusCode.ServiceUnavailable });18}
🧪 Unit Testing
Fast, isolated, and reliable |
🔗 Integration Testing
Stateful workflows made simple |
⚡ Load Testing
Built-in performance testing |
Tests are discovered at build time, not runtime - enabling faster discovery, better IDE integration, and precise resource lifecycle management.
Built for concurrency from day one with [DependsOn]
for test chains, [ParallelLimit]
for resource control, and intelligent scheduling.
The DataSourceGenerator<T>
pattern and custom attribute system let you extend TUnit's capabilities without modifying core framework code.
TUnit works seamlessly across all major .NET development environments:
✅ Fully supported - No additional configuration needed for latest versions
⚙️ Earlier versions: Enable "Use testing platform server mode" in Tools > Manage Preview Features
✅ Fully supported
⚙️ Setup: Enable "Testing Platform support" in Settings > Build, Execution, Deployment > Unit Testing > VSTest
✅ Fully supported
⚙️ Setup: Install C# Dev Kit and enable "Use Testing Platform Protocol"
✅ Full CLI support - Works with dotnet test
, dotnet run
, and direct executable execution
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 |
Coming from NUnit or xUnit? TUnit maintains familiar syntax while adding modern capabilities:
csharp1// Enhanced with TUnit's advanced features2[Test]3[Arguments("value1")]4[Arguments("value2")]5[Retry(3)]6[ParallelLimit<CustomLimit>]7public 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.
The API is mostly stable, but may have some changes based on feedback or issues before v1.0 release.
bash1# Create a new test project with examples2dotnet new install TUnit.Templates && dotnet new TUnit -n "MyAwesomeTests"34# Or add to existing project5dotnet add package TUnit --prerelease
📈 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!
12BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]3Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD78Job=.NET 9.0 Runtime=.NET 9.09
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 |
12BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)3AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX26 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX278Job=.NET 9.0 Runtime=.NET 9.09
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 |
12BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)3AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX26 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX278Job=.NET 9.0 Runtime=.NET 9.09
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 |
12BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]3Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD78Job=.NET 9.0 Runtime=.NET 9.09
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 |
12BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)3AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX26 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX278Job=.NET 9.0 Runtime=.NET 9.09
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 |
12BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)3AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX26 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX278Job=.NET 9.0 Runtime=.NET 9.09
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 |
12BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]3Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD78Job=.NET 9.0 Runtime=.NET 9.09
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)
12BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)3AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX26 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX278Job=.NET 9.0 Runtime=.NET 9.09
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 |
12BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)3AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores4.NET SDK 9.0.3015 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX26 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX278Job=.NET 9.0 Runtime=.NET 9.09
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 |