thomhurst

thomhurst /TUnit

A modern, fast and flexible .NET testing framework

3,210
73
GitHub
Public
MIT License
25.0 MB

Repository Statistics

Key metrics and engagement data

3.2k
Stars
73
Forks
43
Open Issues
0
Releases
1.02
Engagement Rate
Default branch: main

Timeline

Repository has been active for 1 year, 5 months

Repository Created

Last Commit
Recently active

README.md

Repository image

🚀 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.

thomhurst%2FTUnit | Trendshift

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

⚡ Why Choose TUnit?

FeatureTraditional FrameworksTUnit
Test Discovery❌ Runtime reflectionCompile-time generation
Execution Speed❌ Sequential by defaultParallel by default
Modern .NET⚠️ Limited AOT supportFull Native AOT & trimming
Test Dependencies❌ Not supported[DependsOn] chains
Resource Management❌ Manual lifecycleIntelligent 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)

bash
1dotnet new install TUnit.Templates
2dotnet new TUnit -n "MyTestProject"

Manual Installation

bash
1dotnet add package TUnit --prerelease

📖 📚 Complete Documentation & Guides - Everything you need to master TUnit

✨ Key Features

🚀 Performance & Modern Platform

  • 🔥 Source-generated tests (no reflection)
  • ⚡ Parallel execution by default
  • 🚀 Native AOT & trimming support
  • 📈 Optimized for performance

🎯 Advanced Test Control

  • 🔗 Test dependencies with [DependsOn]
  • 🎛️ Parallel limits & custom scheduling
  • 🛡️ Built-in analyzers & compile-time checks
  • 🎭 Custom attributes & extensible conditions

📊 Rich Data & Assertions

  • 📋 Multiple data sources ([Arguments], [Matrix], [ClassData])
  • ✅ Fluent async assertions
  • 🔄 Smart retry logic & conditional execution
  • 📝 Rich test metadata & context

🔧 Developer Experience

  • 💉 Full dependency injection support
  • 🪝 Comprehensive lifecycle hooks
  • 🎯 IDE integration (VS, Rider, VS Code)
  • 📚 Extensive documentation & examples

📝 Simple Test Example

csharp
1[Test]
2public async Task User_Creation_Should_Set_Timestamp()
3{
4 // Arrange
5 var userService = new UserService();
6
7 // Act
8 var user = await userService.CreateUserAsync("[email protected]");
9
10 // Assert - TUnit's fluent assertions
11 await Assert.That(user.CreatedAt)
12 .IsEqualTo(DateTime.Now)
13 .Within(TimeSpan.FromMinutes(1));
14
15 await Assert.That(user.Email)
16 .IsEqualTo("[email protected]");
17}

🎯 Data-Driven Testing

csharp
1[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}
10
11// Matrix testing - tests all combinations
12[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}

🔗 Advanced Test Orchestration

csharp
1[Before(Class)]
2public static async Task SetupDatabase(ClassHookContext context)
3{
4 await DatabaseHelper.InitializeAsync();
5}
6
7[Test, DisplayName("Register a new account")]
8[MethodDataSource(nameof(GetTestUsers))]
9public async Task Register_User(string username, string password)
10{
11 // Test implementation
12}
13
14[Test, DependsOn(nameof(Register_User))]
15[Retry(3)] // Retry on failure
16public async Task Login_With_Registered_User(string username, string password)
17{
18 // This test runs after Register_User completes
19}
20
21[Test]
22[ParallelLimit<LoadTestParallelLimit>] // Custom parallel control
23[Repeat(100)] // Run 100 times
24public async Task Load_Test_Homepage()
25{
26 // Performance testing
27}
28
29// Custom attributes
30[Test, WindowsOnly, RetryOnHttpError(5)]
31public async Task Windows_Specific_Feature()
32{
33 // Platform-specific test with custom retry logic
34}
35
36public class LoadTestParallelLimit : IParallelLimit
37{
38 public int Limit => 10; // Limit to 10 concurrent executions
39}

🔧 Smart Test Control

csharp
1// Custom conditional execution
2public class WindowsOnlyAttribute : SkipAttribute
3{
4 public WindowsOnlyAttribute() : base("Windows only test") { }
5
6 public override Task<bool> ShouldSkip(TestContext testContext)
7 => Task.FromResult(!OperatingSystem.IsWindows());
8}
9
10// Custom retry logic
11public class RetryOnHttpErrorAttribute : RetryAttribute
12{
13 public RetryOnHttpErrorAttribute(int times) : base(times) { }
14
15 public override Task<bool> ShouldRetry(TestInformation testInformation,
16 Exception exception, int currentRetryCount)
17 => Task.FromResult(exception is HttpRequestException { StatusCode: HttpStatusCode.ServiceUnavailable });
18}

🎯 Perfect For Every Testing Scenario

🧪 Unit Testing

csharp
1[Test]
2[Arguments(1, 2, 3)]
3[Arguments(5, 10, 15)]
4public async Task Calculate_Sum(int a, int b, int expected)
5{
6 await Assert.That(Calculator.Add(a, b))
7 .IsEqualTo(expected);
8}

Fast, isolated, and reliable

🔗 Integration Testing

csharp
1[Test, DependsOn(nameof(CreateUser))]
2public async Task Login_After_Registration()
3{
4 // Runs after CreateUser completes
5 var result = await authService.Login(user);
6 await Assert.That(result.IsSuccess).IsTrue();
7}

Stateful workflows made simple

Load Testing

csharp
1[Test]
2[ParallelLimit<LoadTestLimit>]
3[Repeat(1000)]
4public async Task API_Handles_Concurrent_Requests()
5{
6 await Assert.That(await httpClient.GetAsync("/api/health"))
7 .HasStatusCode(HttpStatusCode.OK);
8}

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

🌟 Join thousands of developers modernizing their testing

Downloads Contributors Discussions

🤝 Active Community

🛠️ 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

PackageUse Case
TUnitStart 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:

csharp
1// Enhanced with TUnit's advanced features
2[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.

💡 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

bash
1# Create a new test project with examples
2dotnet new install TUnit.Templates && dotnet new TUnit -n "MyAwesomeTests"
3
4# Or add to existing project
5dotnet add package TUnit --prerelease

🎯 Why Wait? Join the Movement

📈 Performance

Optimized execution Parallel by default Zero reflection overhead

🔮 Future-Ready

Native AOT support Latest .NET features Source generation

🛠️ Developer Experience

Compile-time checks Rich IDE integration Intelligent debugging

🎭 Flexibility

Test 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

1
2BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]
3Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDevMedian
Build_TUnit1,197.4 ms87.87 ms249.28 ms1,105.6 ms
Build_NUnit799.6 ms9.50 ms7.93 ms798.9 ms
Build_xUnit781.1 ms13.08 ms10.93 ms778.8 ms
Build_MSTest850.8 ms16.72 ms26.52 ms841.9 ms

ubuntu-latest

1
2BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
3AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
Build_TUnit1.969 s0.0388 s0.0658 s
Build_NUnit1.495 s0.0269 s0.0238 s
Build_xUnit1.493 s0.0242 s0.0226 s
Build_MSTest1.513 s0.0177 s0.0166 s

windows-latest

1
2BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)
3AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
Build_TUnit1.955 s0.0386 s0.0589 s
Build_NUnit1.563 s0.0294 s0.0275 s
Build_xUnit1.517 s0.0287 s0.0319 s
Build_MSTest1.541 s0.0248 s0.0232 s

Scenario: A single test that completes instantly (including spawning a new process and initialising the test framework)

macos-latest

1
2BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]
3Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
TUnit_AOT75.22 ms0.404 ms0.358 ms
TUnit469.34 ms8.643 ms9.953 ms
NUnit706.44 ms11.712 ms10.383 ms
xUnit734.35 ms14.193 ms18.454 ms
MSTest619.72 ms10.187 ms9.529 ms

ubuntu-latest

1
2BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
3AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
TUnit_AOT27.28 ms0.739 ms2.178 ms
TUnit826.51 ms16.185 ms16.621 ms
NUnit1,288.58 ms16.823 ms15.736 ms
xUnit1,335.37 ms9.836 ms9.201 ms
MSTest1,130.57 ms14.008 ms13.104 ms

windows-latest

1
2BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)
3AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
TUnit_AOT58.46 ms1.789 ms5.219 ms
TUnit895.06 ms17.378 ms26.539 ms
NUnit1,359.08 ms26.245 ms25.777 ms
xUnit1,382.43 ms13.794 ms12.903 ms
MSTest1,176.92 ms11.738 ms9.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

1
2BenchmarkDotNet v0.15.2, macOS Sonoma 14.7.6 (23H626) [Darwin 23.6.0]
3Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), Arm64 RyuJIT AdvSIMD
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
TUnit_AOT243.3 ms11.49 ms33.70 ms
TUnit634.9 ms20.22 ms59.60 ms
NUnit13,981.2 ms279.41 ms583.22 ms
xUnitNANANA
MSTest14,256.3 ms282.60 ms643.62 ms

Benchmarks with issues: RuntimeBenchmarks.xUnit: .NET 9.0(Runtime=.NET 9.0)

ubuntu-latest

1
2BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
3AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
TUnit_AOT74.51 ms0.570 ms0.476 ms
TUnit907.93 ms17.675 ms25.349 ms
NUnit6,290.38 ms15.471 ms14.471 ms
xUnit6,429.98 ms20.035 ms18.740 ms
MSTest6,246.55 ms17.250 ms16.136 ms

windows-latest

1
2BenchmarkDotNet v0.15.2, Windows 10 (10.0.20348.3807) (Hyper-V)
3AMD EPYC 7763 2.44GHz, 1 CPU, 4 logical and 2 physical cores
4.NET SDK 9.0.301
5 [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
6 .NET 9.0 : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2
7
8Job=.NET 9.0 Runtime=.NET 9.0
9
MethodMeanErrorStdDev
TUnit_AOT110.5 ms1.84 ms1.97 ms
TUnit985.9 ms19.53 ms26.73 ms
NUnit7,531.4 ms41.54 ms34.69 ms
xUnit7,585.3 ms18.56 ms17.36 ms
MSTest7,467.0 ms22.65 ms21.19 ms