Unit testing is a fundamental aspect of software development, it ensures that individual components or units of code behave as expected under various conditions.
Popular frameworks like xUnit and NUnit have dominated the .NET ecosystem for years.
However, today, I’m excited to introduce a newcomer in the testing landscape: TUnit.
With its promise of increased flexibility and more granular control, TUnit has the potential to become the new dominant testing framework in .NET.
Why TUnit is a Game-Changer?
TUnit is inspired by xUnit and NUnit, it aims to enhance the beneficial features of both while addressing any potential drawbacks they may have.
It’s built on top of the Microsoft.Testing.Platform, offering a modern testing infrastructure.
After spending some time with TUnit, I believe it’s a game changer for several reasons:
- Source generation: Generates C# code for your tests at compile time, eliminating the need for runtime reflection. This approach not only speeds up test execution but also provides compile-time checks, ensuring that your tests are both reliable and fast.
- Enhanced Setups and Teardowns: Allows you to run multiple teardown methods sequentially, avoiding inheritance issues commonly found in other frameworks. Additionally, you can use setup hooks before and after tests for individual classes or at the assembly level.
- TestContext: Every test has a TestContext object accessible via TestContext.Current. It offers details like the test name, class, properties and categories.
Here’s an dummy example demonstrating how simple it is to implement various setups and teardowns at different levels using TUnit, as well as how easy it is to access the TestContext:
// Setup before each individual test
[Before(Test)]
public async Task Setup()
{
_pingResponse = await _httpClient
.GetAsync("https://localhost/ping");
}
// Cleanup after each individual test
[After(Test)]
public async Task CleanupAfterEachTest()
{
// Example of TestContext
if (TestContext.Current?.Result?.Status == Status.Failed)
{
// Do something
}
_pingResponse = null;
}
// Cleanup after all tests in this class
[After(Class)]
public static async Task CleanupClass()
{
// Do something
}
// Example test method
[Test]
public async Task Ping_ShouldReturnOk()
{
await Assert.That(_pingResponse?.StatusCode)
.IsNotNull()
.And.IsEqualTo(HttpStatusCode.OK);
}
NOTE: Since TUnit is still in its pre-release phase and hasn't reached version 1 yet, you might encounter changes compared to the code snippets I've provided.
Integration and Acceptance Testing
TUnit isn’t just for unit testing, it shines in integration testing as well. Features like test dependencies, retry logic and parallel execution make it ideal for scenarios where the order of execution matters or where tests may need to be retried.
For example, if you need to run a test that depends on another, you can simply use the DependsOn attribute.
[Test]
public async Task AddUser2()
{
//...
}
[Test, DependsOn(nameof(AddUser2))]
public async Task AddItemToBagForUser2()
{
//...
}
Combine that with TUnit's advanced parallelization features and you have full control over test execution, which can be tricky with other frameworks.
Framework Differences
Here is list of enhancements over traditional frameworks like xUnit and NUnit:
- Async Test Parallel Limits: Gives you direct control over how many async tests run concurrently.
- Setups and Teardowns: Run multiple teardown methods sequentially, even in the case of failures. Setup hooks can be used before and after tests, classes, or assemblies.
- TestContext: Every test has a TestContext object accessible via TestContext.Current. It offers details like the test name, class, properties and categories.
- Assertions: Compile-time check assertions, ensuring type safety and reducing runtime errors.
- Source Generation: Supports Native AOT (Ahead-of-Time) and single-file applications through source-generated code.
- Test Dependencies: Define test dependencies without turning off parallelization, making it perfect for integration and acceptance tests.
- Attributes: Attributes like [Retry] and [Arguments] can be applied to tests, classes or assemblies, offering granular control over test behavior.
For full detailed comparison checkout this link: Framework Differences
Getting Started with TUnit
Since TUnit is built on a newer platform, you may need to enable some settings.
Currently, Visual Studio supports only the Preview version.

Rider is supported, but you need to enable the Testing Platform Support option in the settings.

Visual Studio Code is also supported. Using the C# Dev Kit extension, you need to enable Use Testing Platform Protocol.

dotnet CLI fully supports TUnit. Tests should be runnable with dotnet test or dotnet run, providing a seamless experience and allowing you to pass test flags and options effortlessly.
Conclusion
TUnit brings a lot of innovation to the table and I believe this framework has significant potential.
Feel free to check out the project on GitHub and give it a star: TUnit GitHub.
Currently, it's challenging to find much information about it. However, here’s the link to the official documentation for you to explore further: TUnit Documentation.
As always, happy coding!