-
Notifications
You must be signed in to change notification settings - Fork 1
Integration LightBDD xUnit2
Example project: A complete working example is available at
examples/Example.Api/tests/Example.Api.Tests.Component.LightBDD.xUnit2/.
This guide walks you through integrating Kronikol with LightBDD using xUnit 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.
- .NET 10.0 SDK or later
- An ASP.NET Core API project to test (your "Service Under Test")
- Basic familiarity with LightBDD
Create a new xUnit test project:
dotnet new xunit -n MyApi.Tests.Component.LightBDDdotnet add package Kronikol.LightBDD.xUnit2
dotnet add package LightBDD.XUnit2
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package xunit
dotnet add package xunit.runner.visualstudioYour <ItemGroup> should look like this:
<ItemGroup>
<PackageReference Include="Kronikol.LightBDD.xUnit2" Version="2.31.0" />
<PackageReference Include="LightBDD.XUnit2" Version="3.10.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.12" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>LightBDD requires a "scope attribute" to configure the test run. Create Infrastructure/ConfiguredLightBddScopeAttribute.cs:
using System.Reflection;
using LightBDD.Core.Configuration;
using LightBDD.Framework.Configuration;
using LightBDD.XUnit2;
using Kronikol;
using Kronikol.LightBDD.xUnit2;
[assembly: ConfiguredLightBddScope]
[assembly: ClassCollectionBehavior(AllowTestParallelization = false)]
namespace MyApi.Tests.Component.LightBDD.Infrastructure;
internal class ConfiguredLightBddScopeAttribute : LightBddScopeAttribute
{
protected override void OnConfigure(LightBddConfiguration configuration)
{
var testAssembly = Assembly.GetAssembly(typeof(ConfiguredLightBddScopeAttribute))!;
// 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]at the top is required — it tells LightBDD to use this configuration. -
[assembly: ClassCollectionBehavior(AllowTestParallelization = false)]ensures tests run sequentially (required for accurate diagram tracking). -
CreateStandardReportsWithDiagramshooks into LightBDD's native report pipeline, so reports are generated automatically when the test run ends.
Create Infrastructure/BaseFixture.cs. All your test classes will inherit from this:
using LightBDD.XUnit2;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Kronikol.LightBDD.xUnit2;
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 xUnit). -
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 LightBDD.XUnit2;
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 (equivalent to@endpoint:/cakein Gherkin). -
[HappyPath]marks a scenario as a happy path (fromKronikol.LightBDD, defined in theKronikol.LightBDD.Corepackage). - Steps are regular
async Taskmethods — method names are converted to readable text by LightBDD (underscores become spaces).
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 adapter provides a TrackingDiagramOverride class for customising diagrams within a test:
using Kronikol.LightBDD.xUnit2;
// 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. |
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.
- Ensure
[assembly: ConfiguredLightBddScope]is present at the top of your scope attribute file. - Ensure
CreateStandardReportsWithDiagramsis called inOnConfigure. - Check that
AllowTestParallelization = falseis set — parallel tests can cause diagram tracking issues.
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.xUnit2; |
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.
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