-
Notifications
You must be signed in to change notification settings - Fork 1
Integration LightBDD TUnit
Example project: A complete working example is available at
examples/Example.Api/tests/Example.Api.Tests.Component.LightBDD.TUnit/. You can reference it alongside this guide for a fully working implementation.
This guide walks you through integrating Kronikol with LightBDD using TUnit as the test runner. After completing this guide, your LightBDD tests will automatically generate:
- PlantUML sequence diagrams from HTTP traffic between your service and its dependencies
- HTML reports with embedded diagrams (integrated into LightBDD's report pipeline)
- YAML specification files
LightBDD is a BDD framework that lets you write scenarios as C# method calls (given => ..., when => ..., then => ...) using its Runner.RunScenarioAsync pattern, with support for composite steps, tabular data, and rich reporting.
Using xUnit instead of TUnit? See Integration LightBDD xUnit3 or Integration LightBDD xUnit2.
- .NET 8.0 SDK or later
- An ASP.NET Core API project to test (your "Service Under Test")
- Basic familiarity with LightBDD and TUnit
Create a new console project (TUnit requires OutputType=Exe):
dotnet new console -n MyApi.Tests.Component.LightBDDAdd <OutputType>Exe</OutputType> to your .csproj:
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>dotnet add package Kronikol.LightBDD.TUnit
dotnet add package LightBDD.TUnitYour <ItemGroup> should look like this:
<ItemGroup>
<PackageReference Include="Kronikol.LightBDD.TUnit" Version="2.31.0" />
<PackageReference Include="LightBDD.TUnit" Version="3.12.0" />
</ItemGroup>Note: TUnit uses Microsoft.Testing.Platform (not
Microsoft.NET.Test.Sdk). The LightBDD.TUnit package handles the test runner configuration automatically. You do not need to installTUnit,xunit.v3, orMicrosoft.NET.Test.Sdkseparately.
LightBDD for TUnit uses a LightBddScopeAttribute to configure the test run. Create Infrastructure/ConfiguredLightBddScope.cs:
using LightBDD.Core.Configuration;
using LightBDD.Framework.Configuration;
using LightBDD.TUnit;
using Kronikol;
using Kronikol.LightBDD.TUnit;
[assembly: ConfiguredLightBddScope]
namespace MyApi.Tests.Component.LightBDD.Infrastructure;
public class ConfiguredLightBddScopeAttribute : LightBddScopeAttribute
{
protected override void OnConfigure(LightBddConfiguration configuration)
{
// Wire up Kronikol report generation into LightBDD's report pipeline
// Note: The legacy configuration.ReportWritersConfiguration().CreateStandardReportsWithDiagrams()
// overload still works but is deprecated.
configuration.CreateStandardReportsWithDiagrams(
new ReportConfigurationOptions
{
SpecificationsTitle = "My API Specifications"
});
// Optional: Register global setup/teardown for HTTP fakes
configuration.ExecutionExtensionsConfiguration()
.RegisterGlobalTearDown("dispose factory", BaseFixture.DisposeFactory)
.RegisterGlobalSetUp("http fakes", StartHttpFakes, DisposeHttpFakes);
}
private void StartHttpFakes() { /* start your HTTP fakes here */ }
private void DisposeHttpFakes() { /* dispose your HTTP fakes here */ }
}Key points:
-
[assembly: ConfiguredLightBddScope]is required — it registers TUnit assembly hooks that initialise and finalise LightBDD. - The scope class extends
LightBddScopeAttribute(which uses TUnit's[Before(Assembly)]/[After(Assembly)]hooks internally). -
CreateStandardReportsWithDiagramshooks into LightBDD's native report pipeline, so reports are generated automatically when the test run ends.
Note: The
LightBddConfiguration.CreateStandardReportsWithDiagrams()overload (called directly onLightBddConfigurationrather than onReportWritersConfiguration()) also registers aTUnitArgumentCaptureDecoratorthat automatically captures raw test method arguments for rich sub-table rendering of complex objects. To use it, callconfiguration.CreateStandardReportsWithDiagrams(options)instead ofconfiguration.ReportWritersConfiguration().CreateStandardReportsWithDiagrams(options).
Create Infrastructure/BaseFixture.cs. All your test classes will inherit from this:
using LightBDD.TUnit;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Kronikol.LightBDD.TUnit;
namespace MyApi.Tests.Component.LightBDD.Infrastructure;
public abstract class BaseFixture : FeatureFixture, 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 LightBddTestTrackingMessageHandlerOptions
{
CallerName = ServiceUnderTestName,
PortsToServiceNames =
{
{ 80, ServiceUnderTestName },
{ 5001, "Downstream Service A" }
}
});
});
});
}
protected BaseFixture()
{
Client = SFactory!.CreateTestTrackingClient(
new LightBddTestTrackingMessageHandlerOptions
{
FixedNameForReceivingService = ServiceUnderTestName
});
}
public void Dispose() => Client.Dispose();
public static void DisposeFactory() => SFactory?.Dispose();
}Key points:
- The static constructor creates the
WebApplicationFactoryonce for all tests. - Each test instance gets its own
HttpClientvia the instance constructor. - The test inherits from
FeatureFixture(LightBDD's base class for TUnit). -
LightBddTestTrackingMessageHandlerOptionsautomatically resolves the current test context using LightBDD'sScenarioExecutionContext.
LightBDD uses partial classes — one file for the scenario definitions, one for the step implementations.
using LightBDD.Framework.Scenarios;
using Kronikol.LightBDD.TUnit;
namespace MyApi.Tests.Component.LightBDD.Scenarios;
[FeatureDescription("/cake")]
public partial class Cake_Feature
{
[HappyPath]
[Scenario]
public async Task Calling_Create_Cake_Endpoint_Successfully()
{
await Runner.RunScenarioAsync(
given => A_valid_post_request_for_the_Cake_endpoint(),
when => The_request_is_sent_to_the_cake_post_endpoint(),
then => The_response_should_be_successful());
}
[Scenario]
public async Task Calling_Create_Cake_Endpoint_Without_Eggs()
{
await Runner.RunScenarioAsync(
given => A_valid_post_request_for_the_Cake_endpoint(),
but => The_request_body_is_missing_eggs(),
when => The_request_is_sent_to_the_cake_post_endpoint(),
then => The_response_http_status_should_be_bad_request());
}
}using System.Net;
using System.Net.Http.Json;
using MyApi.Tests.Component.LightBDD.Infrastructure;
namespace MyApi.Tests.Component.LightBDD.Scenarios;
public partial class Cake_Feature : BaseFixture
{
private HttpResponseMessage? _response;
private async Task A_valid_post_request_for_the_Cake_endpoint()
{
// Set up your request data using Client
}
private async Task The_request_body_is_missing_eggs()
{
// Modify request
}
private async Task The_request_is_sent_to_the_cake_post_endpoint()
{
_response = await Client.PostAsJsonAsync("cake", /* your request */);
}
private async Task The_response_should_be_successful()
{
_response!.StatusCode.Should().Be(HttpStatusCode.OK);
}
private async Task The_response_http_status_should_be_bad_request()
{
_response!.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
}Key points:
-
[FeatureDescription("/cake")]sets the endpoint label in the report. -
[HappyPath]marks a scenario as a happy path (fromKronikol.LightBDD.TUnit). -
[Scenario]is an alias for TUnit's[Test]attribute provided byLightBDD.TUnit. - Steps are regular
async Taskmethods — method names are converted to readable text by LightBDD (underscores become spaces).
Since TUnit uses Microsoft.Testing.Platform, you can run tests with:
dotnet runOr:
dotnet testAfter 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 |
LightBDD's TUnit adapter provides a TrackingDiagramOverride class for customising diagrams within a test:
using Kronikol.LightBDD.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, LightBDD automatically detects the boundary between GIVEN steps and WHEN/THEN steps. HTTP calls made during GIVEN steps are wrapped in a visual "Setup" partition in the diagram — no manualStartAction()call is needed.
Tip:
InsertTestDelimiteris particularly useful when using TabularAttributes, where a single scenario runs multiple iterations. Insert a delimiter between each iteration to clearly separate them in the diagram.
Passed to CreateStandardReportsWithDiagrams:
| Property | Default | Description |
|---|---|---|
SpecificationsTitle |
"Service Specifications" |
Title shown at the top of reports |
PlantUmlServerBaseUrl |
"https://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 |
Stylesheets.VioletThemeStyleSheet |
Custom CSS appended to specs HTML |
ExcludedHeaders |
[] |
HTTP headers to exclude from diagrams |
SeparateSetup |
false |
When true, HTTP calls made during GIVEN steps 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 (your SUT) |
PortsToServiceNames |
Dictionary mapping port numbers to friendly service names. Unmapped ports appear as localhost_80, localhost_5001, etc. |
If you're migrating from the Kronikol.LightBDD.xUnit3 package, the key changes are:
| Aspect | xUnit v3 | TUnit |
|---|---|---|
| Package | Kronikol.LightBDD.xUnit3 |
Kronikol.LightBDD.TUnit |
| LightBDD package | LightBDD.XUnit3 |
LightBDD.TUnit |
| Test runner | xUnit v3 | TUnit (Microsoft.Testing.Platform) |
| Scope setup | [assembly: TestPipelineStartup(typeof(...))] |
[assembly: ConfiguredLightBddScope] |
| Scope base class |
LightBddScope (class) |
LightBddScopeAttribute (attribute) |
| Test SDK |
Microsoft.NET.Test.Sdk + xunit.v3
|
Not needed (TUnit handles this) |
| Namespace | Kronikol.LightBDD.xUnit3 |
Kronikol.LightBDD.TUnit |
All other concepts (FeatureFixture, Runner.RunScenarioAsync, CompositeStep, TrackingDiagramOverride, etc.) remain the same.
- Ensure
[assembly: ConfiguredLightBddScope](or your custom scope attribute) is present at the top of your scope file. - Ensure
CreateStandardReportsWithDiagramsis called inOnConfigure. - Ensure your csproj has
<OutputType>Exe</OutputType>— TUnit requires executable output.
If any test has failed, the specifications files will be blank by design (they only generate on a fully passing test run). The TestRunReport.html will still be generated.
If the compiler is asking you for a Func<Assembly, int> testCountResolver parameter, you are importing the wrong namespace. The core namespace Kronikol.LightBDD exposes a lower-level overload that requires this parameter. The framework-specific namespace provides an overload that supplies it automatically:
| Wrong namespace | Correct namespace |
|---|---|
using Kronikol.LightBDD; |
using Kronikol.LightBDD.TUnit; |
The framework-specific overload internally calls assembly.CountNumberOfTestsInAssembly() to count [Scenario] methods in your test assembly. This count is used to detect when all tests have completed so reports are written only once at the end of the run, rather than being overwritten after each individual test.
[Scenario] is an alias for TUnit's [Test] attribute, provided by LightBDD.TUnit via a global using in its .props file. Ensure your project references LightBDD.TUnit (either directly or via Kronikol.LightBDD.TUnit).
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 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
Reference