TUnit Documentation

repository·main·Indexed 26 days ago

https://github.com/thomhurst/tunit

A modern .NET testing framework built on Microsoft.Testing.Platform, featuring high performance, compile-time test discovery via source generators, and full Native AOT compatibility. TUnit provides a modular ecosystem including TUnit.Mocks for AOT-compatible mocking, fluent async assertions with Assert.That, and first-class integrations for ASP.NET Core, Aspire, and Playwright. It supports data-driven testing via [Arguments] and [MatrixDataSource], as well as advanced execution control through parallelism and sequencing attributes.

Tokens
177.2K
Snippets
534
Records
726
Agent score
86%

What's inside TUnit

  1. Overview of TUnit

    main

    TUnit is a testing framework for C# / .NET designed for unit testing, integration testing, acceptance testing, and other testing scenarios. It is built on Microsoft.Testing.Platform to provide a simpler and more extensible testing experience.

    Key features include:

    • Flexible test data injection (new or shared instances).
    • Lifecycle hooks for running code before and after tests.
    • Minimal opinions on test style.
    • High performance achieved through source generation and compile-time optimizations.
  2. Review TUnit performance benchmarks

    main

    TUnit provides performance benchmarks comparing its runtime and build speed against other .NET testing frameworks like xUnit v3, NUnit, and MSTest. Benchmarks are categorized into runtime execution patterns and build performance.

    Runtime Benchmark Scenarios

    • AsyncTests: Realistic async/await patterns with I/O simulation.
    • DataDrivenTests: Parameterized tests with multiple data sources.
    • MassiveParallelTests: Parallel execution stress tests.
    • MatrixTests: Combinatorial test generation and execution.
    • ScaleTests: Large test suites (150+ tests) measuring scalability.
    • SetupTeardownTests: Expensive test fixtures with setup/teardown overhead.

    Build Benchmarks

    • Build Performance: Comparison of compilation times.
  3. Choose between Source Generation and Reflection modes

    main

    TUnit operates in two modes to balance performance and compatibility:

    • Source Generation Mode (Default): Uses compile-time generation for test discovery and execution. It provides optimal performance, type safety, and is required for full Native AOT compatibility.
    • Reflection Mode: Uses runtime reflection for discovery. This is necessary for testing scenarios where tests are generated by other tools (e.g., .razor files in bUnit) or when using languages like F# or VB.NET.
  4. Hook into the TUnit test lifecycle

    main

    TUnit provides four primary mechanisms to hook into different stages of the test lifecycle (Discovery and Execution):

    1. Hook Attributes: Method-based hooks using attributes like [Before] and [After].
    2. Event Receivers: Object-based event subscriptions using interfaces like ITestStartEventReceiver.
    3. Initialization Interfaces: Async object setup using IAsyncInitializer or IAsyncDiscoveryInitializer.
    4. Disposal Interfaces: Resource cleanup using standard IDisposable or IAsyncDisposable interfaces.
  5. Compare TUnit.Mocks against other .NET mocking libraries

    main

    TUnit.Mocks uses a source-generated approach at compile time, making it compatible with Native AOT and IL trimming. This contrasts with popular libraries like Moq, NSubstitute, and FakeItEasy, which rely on runtime proxy generation via Castle.DynamicProxy and are not AOT compatible.

    LibraryApproachAOT Compatible
    TUnit.MocksSource-generated at compile time✅ Yes
    ImposterSource-generated at compile time✅ Yes
    MockolateSource-generated at compile time✅ Yes
    MoqRuntime proxy via Castle.DynamicProxy❌ No
    NSubstituteRuntime proxy via Castle.DynamicProxy❌ No
    FakeItEasyRuntime proxy via Castle.DynamicProxy❌ No
  6. Understand TUnit benchmark categories

    main

    TUnit performance is measured across several specific test patterns to ensure real-world applicability:

    • DataDrivenTests: Measures performance of parameterized tests using [Arguments].
    • AsyncTests: Measures the overhead of the async/await pattern.
    • ScaleTests: Measures scalability, memory efficiency, and parallel execution with 1000+ test methods.
    • MatrixTests: Measures combinatorial test generation using [MatrixDataSource] and [Matrix] attributes.
    • MassiveParallelTests: Stress tests resource contention and thread safety with 100+ concurrent tests.
    • Build Benchmarks: Measures the impact of compilation, including clean/incremental builds and source generator overhead.
  7. Skip a test at runtime using SkipTestException

    main

    If a test cannot run due to a specific runtime condition (e.g., a missing external dependency), throw SkipTestException. This ensures the test is reported as skipped in the test results instead of being reported as a failure.

    [Test]
    public async Task RequiresExternalService()
    {
        if (!await IsServiceAvailable())
        {
            throw new SkipTestException("Service is not available");
        }
    
        // Test logic
    }
  8. Create and register custom log sinks

    main

    TUnit uses a sink-based architecture where all output is routed through registered ILogSink implementations. You can create custom sinks to write logs to files, external services (like Seq), or other destinations.

    To register a sink, use a [Before(TestDiscovery)] hook to ensure it is active before tests run. Sinks implementing IDisposable or IAsyncDisposable are automatically cleaned up when the test session ends.

    using TUnit.Core;
    using TUnit.Core.Logging;
    
    public class TestSetup
    {
        [Before(TestDiscovery)]
        public static void SetupLogging()
        {
            // Register by instance
            TUnitLoggerFactory.AddSink(new FileLogSink("test-output.log"));
    
            // Or register by type
            TUnitLoggerFactory.AddSink<DebugLogSink>();
        }
    }
  9. Choose between IClassConstructor and DependencyInjectionDataSourceAttribute

    main

    Select the appropriate mechanism based on your dependency management needs:

    NeedUse
    Full DI container with scoped lifetimesDependencyInjectionDataSourceAttribute<TScope>
    Simple manual construction or lightweight containerIClassConstructor
    Disposal / cleanup after testsEither — implement IAsyncDisposable on the scope or use event-subscribing interfaces on the constructor
  10. Use Argument Matchers in TUnit.Mocks

    main

    Argument matchers allow you to control which method calls match during mock setup or verification. TUnit.Mocks automatically imports the Arg class via global using static, so you can call matcher methods directly without the Arg. prefix.

    Common use cases include:

    • Setup: Defining return values or exceptions based on specific argument patterns.
    • Verification: Checking if a method was called with arguments that satisfy certain criteria.