-
Notifications
You must be signed in to change notification settings - Fork 1
Integration TUnit
Example project: A complete working example is available at
examples/Example.Api/tests/Example.Api.Tests.Component.TUnit/.
This guide walks you through integrating TestTrackingDiagrams with TUnit. After completing this guide, your TUnit tests will automatically generate:
- PlantUML sequence diagrams from HTTP traffic between your service and its dependencies
- HTML reports with embedded diagrams
- YAML specification files
- .NET 10.0 SDK or later
- An ASP.NET Core API project to test (your "Service Under Test")
- Basic familiarity with TUnit
Create a new console project (TUnit requires OutputType=Exe):
dotnet new console -n MyApi.Tests.Componentdotnet add package TestTrackingDiagrams.TUnit
dotnet add package TUnit
dotnet add package Microsoft.AspNetCore.Mvc.TestingYour <ItemGroup> should look like this:
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TestTrackingDiagrams.TUnit" Version="2.0.78-beta" />
<PackageReference Include="TUnit" Version="1.33.0" />
<PackageReference Include="coverlet.collector" Version="8.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>Note: TUnit uses Microsoft.Testing.Platform (not
Microsoft.NET.Test.Sdk). The TUnit meta package handles the test runner configuration automatically.
TUnit uses [Before(Assembly)] / [After(Assembly)] hooks for global setup and teardown.
Create Infrastructure/TestRun.cs:
using TestTrackingDiagrams;
using TestTrackingDiagrams.TUnit;
using TUnit.Core;
namespace MyApi.Tests.Component.Infrastructure;
public class TestRun : DiagrammedTestRun
{
[Before(Assembly)]
public static void GlobalSetup(AssemblyHookContext context)
{
Setup();
// Optional: start any HTTP fakes here
}
[After(Assembly)]
public static void GlobalTeardown(AssemblyHookContext context)
{
EndRunTime = DateTime.UtcNow;
// Generate reports when the test run ends
TUnitReportGenerator.CreateStandardReportsWithDiagrams(
TestContexts,
StartRunTime,
EndRunTime,
new ReportConfigurationOptions
{
SpecificationsTitle = "My API Specifications"
});
// Optional: dispose HTTP fakes here
}
}Key points:
-
[Before(Assembly)]and[After(Assembly)]run once at the start and end of the test assembly. - The
AssemblyHookContextparameter is required by TUnit for assembly-level hooks. - Call
Setup()in[Before(Assembly)]— this records theStartRunTime.
Create Infrastructure/BaseFixture.cs:
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using TestTrackingDiagrams.TUnit;
namespace MyApi.Tests.Component.Infrastructure;
public abstract class BaseFixture : DiagrammedComponentTest, IDisposable
{
private static readonly WebApplicationFactory<Program>? SFactory;
protected HttpClient Client { get; }
private const string ServiceUnderTestName = "My API";
static BaseFixture()
{
SFactory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.TrackDependenciesForDiagrams(new TUnitTestTrackingMessageHandlerOptions
{
CallerName = ServiceUnderTestName,
PortsToServiceNames =
{
{ 80, ServiceUnderTestName },
{ 5001, "Downstream Service A" }
}
});
});
});
}
protected BaseFixture()
{
Client = SFactory!.CreateTestTrackingClient(
new TUnitTestTrackingMessageHandlerOptions
{
FixedNameForReceivingService = ServiceUnderTestName
});
}
public void Dispose() => Client.Dispose();
}Key points:
-
DiagrammedComponentTestprovides an[After(Test)]hook that automatically enqueues theTestContextfor report collection after each test. -
TUnitTestTrackingMessageHandlerOptionsuses TUnit'sTestContext.Currentto resolve the current test's identity. - TUnit creates a new instance per test by default (no additional configuration needed).
Tests are written as regular TUnit [Test] methods. Use the [Endpoint] and [HappyPath] attributes to add metadata for the report.
using TestTrackingDiagrams.TUnit;
namespace MyApi.Tests.Component.Scenarios;
[Endpoint("/cake")]
public partial class Cake_Feature
{
[Test]
[HappyPath]
public async Task Calling_Create_Cake_Endpoint_Returns_Cake()
{
await Given_a_valid_post_request_for_the_Cake_endpoint();
await When_the_request_is_sent_to_the_cake_post_endpoint();
await Then_the_response_should_be_successful();
}
[Test]
public async Task Calling_Create_Cake_Endpoint_Without_Eggs_Returns_Bad_Request()
{
await Given_a_valid_post_request_for_the_Cake_endpoint();
await But_the_request_body_is_missing_eggs();
await When_the_request_is_sent_to_the_cake_post_endpoint();
await Then_the_response_http_status_should_be_bad_request();
}
}using System.Net;
using System.Net.Http.Json;
using MyApi.Tests.Component.Infrastructure;
namespace MyApi.Tests.Component.Scenarios;
public partial class Cake_Feature : BaseFixture
{
private HttpResponseMessage? _response;
private async Task Given_a_valid_post_request_for_the_Cake_endpoint()
{
// Build your request using Client
}
private async Task But_the_request_body_is_missing_eggs()
{
// Modify request
}
private async Task When_the_request_is_sent_to_the_cake_post_endpoint()
{
_response = await Client.PostAsJsonAsync("cake", /* request */);
}
private async Task Then_the_response_should_be_successful()
{
_response!.StatusCode.Should().Be(HttpStatusCode.OK);
}
private async Task Then_the_response_http_status_should_be_bad_request()
{
_response!.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
}Key points:
-
[Endpoint("/cake")]— Sets the endpoint label for this feature group in the report. This attribute maps to TUnit'sPropertyAttribute. -
[HappyPath]— Marks a scenario as a happy path (filterable in the HTML report). This maps to TUnit'sCategoryAttribute. - Class and method names are converted from underscore-separated / PascalCase to human-readable format in reports.
dotnet runNote: Since TUnit uses Microsoft.Testing.Platform, you run tests with
dotnet runinstead ofdotnet test. However,dotnet testalso works if you have the TUnit meta package installed.
After the tests complete, check the bin/Debug/net10.0/Reports/ folder:
| File | Description |
|---|---|
Specifications.html |
HTML specifications with embedded PlantUML sequence diagrams |
TestRunReport.html |
HTML test run report with diagrams and execution summary |
Specifications.yml |
YAML specifications |
┌─────────────────────────────────┐
│ TestRun │ ← [Before(Assembly)] / [After(Assembly)]
│ : DiagrammedTestRun │ Generates reports in [After(Assembly)]
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ BaseFixture │ ← Creates tracked HttpClient
│ : DiagrammedComponentTest │ Enqueues TestContext via [After(Test)]
│ IDisposable │
└─────────────┬───────────────────┘
│ inherited by
▼
┌─────────────────────────────────┐
│ Cake_Feature : BaseFixture │ ← Your test class with [Test] methods
└─────────────────────────────────┘
You can customise diagrams within a test using TrackingDiagramOverride:
using TestTrackingDiagrams.TUnit;
// Insert a delimiter between multiple requests in the diagram
TrackingDiagramOverride.InsertTestDelimiter("Step 1");
// Insert raw PlantUML markup
TrackingDiagramOverride.InsertPlantUml("note over MyApi : Custom note");
// Override the start/end of diagram generation
TrackingDiagramOverride.StartOverride();
TrackingDiagramOverride.EndOverride();
// Explicitly mark the boundary between setup and action phases
TrackingDiagramOverride.StartAction();Setup separation: When
SeparateSetup = trueis set onReportConfigurationOptions, HTTP calls made beforeStartAction()are wrapped in a visual "Setup" partition in the diagram.
| Property | Default | Description |
|---|---|---|
SpecificationsTitle |
"Specifications" |
Title shown at the top of reports |
PlantUmlServerBaseUrl |
"https://www.plantuml.com/plantuml" |
PlantUML server URL |
HtmlSpecificationsFileName |
"Specifications" |
Output filename for specs HTML |
HtmlTestRunReportFileName |
"TestRunReport" |
Output filename for test run HTML |
YamlSpecificationsFileName |
"Specifications" |
Output filename for YAML specs |
HtmlSpecificationsCustomStyleSheet |
null |
Custom CSS appended to specs HTML |
ExcludedHeaders |
[] |
HTTP headers to exclude from diagrams |
SeparateSetup |
false |
When true, HTTP calls made before StartAction() are wrapped in a visual "Setup" partition in the diagram |
HighlightSetup |
true |
When true (and SeparateSetup is enabled), the setup partition is rendered with a background colour |
| Property | Description |
|---|---|
CallerName |
Display name for the service making outgoing HTTP calls |
FixedNameForReceivingService |
Display name for the service receiving requests |
PortsToServiceNames |
Dictionary mapping port numbers to friendly service names. Unmapped ports appear as localhost_80, localhost_5001, etc. |
When your SUT calls downstream HTTP services, those calls must flow through TestTrackingMessageHandler to produce proper HTTP-style diagram arrows (with method, status code, headers, body). Do not mock service client interfaces and use MessageTracker to manually log HTTP interactions — this produces event-style (blue) arrows that are misleading.
Recommended approaches:
-
In-memory fake APIs —
WebApplicationFactoryinstances that serve canned responses (see Example Project) -
JustEat HttpClient Interception — handler-level interception, chain with
TestTrackingMessageHandler -
WireMock.Net — real HTTP server on a random port, map in
PortsToServiceNames
See Tracking Dependencies#faking-dependencies-getting-proper-http-tracking for detailed examples of each approach.
If you're migrating from xUnit v3, the key changes are:
| Aspect | xUnit v3 | TUnit |
|---|---|---|
| Package | TestTrackingDiagrams.xUnit3 |
TestTrackingDiagrams.TUnit |
| Test runner | xUnit v3 (out-of-process) | TUnit (Microsoft.Testing.Platform) |
| Test attribute |
[Fact] / [Theory]
|
[Test] |
| Lifecycle | Collection fixture (ICollectionFixture<T>) |
[Before(Assembly)] / [After(Assembly)] hooks |
| Constructor safety | Guaranteed (fixture init blocks constructors) | Not guaranteed — must self-initialize |
| Base class |
DiagrammedComponentTest (enqueues TestContext via IDisposable.Dispose) |
DiagrammedComponentTest (enqueues TestContext via [After(Test)] hook) |
| Options type | XUnitTestTrackingMessageHandlerOptions |
TUnitTestTrackingMessageHandlerOptions |
| Report generator | XUnitReportGenerator |
TUnitReportGenerator |
| Test SDK |
Microsoft.NET.Test.Sdk + xunit.v3
|
Not needed (TUnit handles this) |
| Output type | <OutputType>Exe</OutputType> |
<OutputType>Exe</OutputType> (handled by TUnit metapackage) |
| Execution | dotnet test |
dotnet run or dotnet test
|
| Namespace | TestTrackingDiagrams.xUnit3 |
TestTrackingDiagrams.TUnit |
- Ensure
[After(Assembly)]callsTUnitReportGenerator.CreateStandardReportsWithDiagrams. - Ensure your test classes inherit from
BaseFixture(which inherits fromDiagrammedComponentTest). - Ensure
[Before(Assembly)]callsSetup()to recordStartRunTime.
- Make sure each test class inherits from
DiagrammedComponentTest(directly or viaBaseFixture). The base class provides an[After(Test)]hook that enqueues theTestContextfor report collection.
If any test has failed, the specifications files will be blank by design. The TestRunReport.html will still be generated.
This is usually caused by TestContext.Current being null when the tracking handler invokes the fetcher. However, this is by design — when the fetcher throws, the handler catches the exception and forwards the request without tracking. The handler instance is not broken; subsequent requests (during actual test execution) will resolve TestContext.Current normally and produce diagrams.
Common causes of genuinely missing diagrams:
- Test classes don't inherit from
DiagrammedComponentTest(soTestContextis never enqueued) -
CreateTestTrackingClientisn't used (so the outer handler doesn't attach test identity headers) -
TrackDependenciesForDiagramsisn't called inConfigureTestServices(so the inner handler doesn't log requests)
Note: Do not replace
CurrentTestInfo.Fetcherwith a non-throwing variant. The exception is the correct signal — it tells the tracking infrastructure to skip unattributable traffic and allowsTestInfoResolverto fall through toTestIdentityScope.Current/GlobalFallbackwhen running on background threads that have an explicit scope set.
Unlike xUnit's ICollectionFixture<T> (which guarantees the fixture is fully initialized before any test constructor runs), TUnit's [Before(Assembly)] runs before test methods but doesn't guarantee execution before test class constructors.
If your [Before(Assembly)] hook starts infrastructure (e.g. in-memory database servers, HTTP fakes) and your BaseFixture static constructor reads that infrastructure, you may hit NullReferenceException on some tests because the constructor ran before the hook finished.
Fix: Don't rely on [Before(Assembly)] for infrastructure that the static factory constructor needs. Instead, initialize it defensively inside the factory creation:
static BaseFixture()
{
// Initialize infrastructure HERE, not in [Before(Assembly)]
if (_fakeServer is null)
{
_fakeServer = new FakeServer();
_fakeServer.Start();
}
SFactory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.ConfigureWithFakeServer(_fakeServer);
});
});
}Or use a Lazy<WebApplicationFactory<T>> pattern that defers creation until first test access.
Getting Started
Common Tasks
Integration Guides
- Integration xUnit3
- Integration xUnit2
- Integration NUnit
- Integration MSTest
- Integration TUnit
- Integration BDDfy xUnit3
- Integration LightBDD xUnit2
- Integration LightBDD xUnit3
- Integration LightBDD TUnit
- Integration ReqNRoll xUnit2
- Integration ReqNRoll xUnit3
- Integration ReqNRoll TUnit
Extensions
- Integration AtlasDataApi Extension
- Integration BigQuery Extension
- Integration Bigtable Extension
- Integration BlobStorage Extension
- Integration ClickHouse Extension
- Integration CloudStorage Extension
- Integration CosmosDB Extension
- Integration Dapper Extension
- Integration DynamoDB Extension
- Integration EF Core Relational Extension
- Integration Elasticsearch Extension
- Integration EventBridge Extension
- Integration EventHubs Extension
- Integration Grpc Extension
- Integration Kafka Extension
- Integration MassTransit Extension
- Integration MongoDB Extension
- Integration MySqlConnector Extension
- Integration Npgsql Extension
- Integration Oracle Extension
- Integration PubSub Extension
- Integration Redis Extension
- Integration S3 Extension
- Integration ServiceBus Extension
- Integration SNS Extension
- Integration Spanner Extension
- Integration SqlClient Extension
- Integration Sqlite Extension
- Integration SQS Extension
- Integration StorageQueues Extension
- Integration OpenTelemetry Extension
- Integration DispatchProxy Extension
- Integration MediatR Extension
- Integration PlantUML IKVM
Configuration
- Tracking Dependencies
- Tracking Custom Dependencies
- HTTP Tracking Setup
- Report Configuration
- Diagram Customisation
- Phase-Aware Tracking
- Content Formatting
- PlantUML Server Configuration
Features
- Generated Reports
- Search Syntax
- Component Diagrams
- PlantUML Browser Rendering
- Inline SVG Rendering
- Internal Flow Tracking
- Tags and Attributes
- Excluding Requests
- Excluded Headers
- Multi-Host Test Architectures
- Event-Driven Architecture Testing
- Service Bus Tracking Patterns
- Background Thread Correlation
- Parallel-Safe Background Correlation
- Event & Message Tracking
- Assertion Tracking
- Step Tracking
- Tabular Attributes
- Large Response and Diagram Handling
- Diagnostics and Debugging
- CI Summary Integration
- CI Artifact Upload
- Merging Parallel Reports
Reference