-
Notifications
You must be signed in to change notification settings - Fork 1
Step Tracking
Added in v2.30.29
Step tracking lets you define BDD-style steps (Given/When/Then/But) as attributed methods. At build time, the TestTrackingDiagrams.StepTracking package uses IL weaving to automatically instrument these methods — recording their execution, parameters, timing, and pass/fail status in your test reports.
This gives you the expressive step structure of BDD frameworks without any framework dependency.
- Install the package in your test project:
<PackageReference Include="TestTrackingDiagrams.StepTracking" Version="2.30.30" />- Add the assembly-level opt-in attribute anywhere in your test project:
using TestTrackingDiagrams.Tracking;
[assembly: TrackSteps]- Decorate your step methods with BDD attributes:
public class OrderTests : TestBase
{
[GivenStep]
public void A_customer_exists() { /* setup logic */ }
[GivenStep]
public void The_customer_has_a_basket_with_items(int itemCount)
{
// setup logic with parameters
}
[WhenStep]
public void The_customer_places_an_order() { /* action */ }
[ThenStep]
public void An_order_confirmation_is_sent() { /* assertion */ }
[Fact]
public async Task Customer_can_place_order()
{
A_customer_exists();
The_customer_has_a_basket_with_items(3);
The_customer_places_an_order();
An_order_confirmation_is_sent();
}
}The test report will show structured steps:
| Keyword | Step | Status | Duration |
|---|---|---|---|
| Given | A customer exists | ✓ Passed | 2ms |
| And | The customer has a basket with items | ✓ Passed | 1ms |
| When | The customer places an order | ✓ Passed | 45ms |
| Then | An order confirmation is sent | ✓ Passed | 3ms |
Note: The
TrackStepsAttribute,GivenStepAttribute,WhenStepAttribute,ThenStepAttribute,ButStepAttribute, andStepAttributetypes are auto-generated into your project at build time — no manual type definitions needed.
| Attribute | Keyword | Phase Transition | Purpose |
|---|---|---|---|
[GivenStep] |
Given | → Setup | Preconditions, test data setup |
[WhenStep] |
When | → Action | The action under test |
[ThenStep] |
Then | → Action | Assertions and verification |
[ButStep] |
But | → Setup | Negative preconditions ("But the user is not an admin") |
[Step] |
(none) | (none) | Generic step with no keyword |
All step attributes accept a Description property to override the humanized method name:
[GivenStep(Description = "A user with admin privileges")]
public void SetupAdminUser() { /* ... */ }Without Description, the method name is humanized automatically: SetupAdminUser → "Setup admin user".
Consecutive methods with the same keyword are automatically sequenced using And:
[GivenStep] public void A_user_exists() { } // → "Given"
[GivenStep] public void The_user_is_active() { } // → "And" (auto-sequenced)
[GivenStep] public void The_user_has_a_session() { } // → "And" (auto-sequenced)
[WhenStep] public void The_user_logs_out() { } // → "When" (new keyword resets)
[ThenStep] public void The_session_is_destroyed() { } // → "Then" (new keyword resets)
[ThenStep] public void A_logout_event_is_raised() { } // → "And" (auto-sequenced)The same sequencing applies to [ButStep]:
[ButStep] public void The_user_is_not_admin() { } // → "But"
[ButStep] public void The_user_has_no_mfa() { } // → "And" (auto-sequenced)You never need to explicitly write "And" — it is always inferred from repetition.
Method parameters are automatically captured and displayed in the step report:
[GivenStep]
public void A_user_with_name_and_age(string name, int age)
{
// parameters "name" and "age" are captured at runtime
}
// Call:
A_user_with_name_and_age("Alice", 30);The report shows:
| Keyword | Step | Parameters |
|---|---|---|
| Given | A user with name and age | name="Alice", age="30" |
Both value types and reference types are captured. Value types are boxed for display.
Method names are converted to sentence-case prose using these rules:
| Method Name | Humanized Text |
|---|---|
TheUserLogsIn |
The user logs in |
A_User_Exists |
A user exists |
GetHTTPResponse |
Get http response |
Setup_Admin_User |
Setup admin user |
The algorithm:
- Replaces underscores with spaces
- Splits PascalCase at lowercase→uppercase boundaries
- Handles acronym sequences (e.g. "HTTP" stays together until the next word)
- Applies sentence case (first letter uppercase, rest lowercase)
When a method name starts with the same keyword as its attribute, the keyword prefix is automatically stripped to avoid duplication in reports:
| Attribute | Method Name | Step Text |
|---|---|---|
[GivenStep] |
GivenTheyGo |
They go |
[WhenStep] |
When_they_go |
They go |
[ThenStep] |
ThenItWorks |
It works |
[WhenStep] |
WheneverTheyGo |
Whenever they go |
The match is whole-word only — WheneverTheyGo does not match because "Whenever" ≠ "When ". Similarly, ThenceTheyGo is not stripped because "Thence" ≠ "Then ".
This allows natural method naming conventions like GivenAUserExists() or When_the_api_is_called() without keyword repetition in the report output.
Step attributes automatically set the ambient Phase-Aware Tracking context:
-
[GivenStep]and[ButStep]→TestPhase.Setup -
[WhenStep]and[ThenStep]→TestPhase.Action
This means your tracking extensions (SQL, HTTP, Redis, etc.) automatically adjust their verbosity based on which step is currently executing. Setup noise is reduced, while action-phase calls get full detail.
Phase transitions are enabled by default. To disable:
StepCollector.Options = new StepTrackingOptions { WhenTriggersAction = false };See Phase-Aware Tracking for details on how extensions respond to phase changes.
When Assertion Tracking is also enabled, assertions that execute within an active step are automatically recorded as sub-steps of that step:
[ThenStep]
public void The_order_total_is_correct()
{
_order.Total.Should().Be(99.99m); // ✓ recorded as sub-step
_order.Currency.Should().Be("GBP"); // ✓ recorded as sub-step
}The report shows:
| Keyword | Step | Sub-Steps |
|---|---|---|
| Then | The order total is correct | ✓ _order.Total.Should().Be(99.99m) |
| ✓ _order.Currency.Should().Be("GBP") |
This requires both TestTrackingDiagrams.StepTracking and TestTrackingDiagrams.AssertionTracking packages to be installed.
Steps can be nested — calling a step method from within another step method creates a sub-step hierarchy:
[GivenStep]
public void A_complete_test_environment()
{
Setup_database();
Seed_test_data();
}
[Step]
public void Setup_database() { /* ... */ }
[Step]
public void Seed_test_data() { /* ... */ }The inner steps become sub-steps of the outer step in the report.
The TestTrackingDiagrams.StepTracking NuGet package contains:
-
An MSBuild .targets file that auto-generates the step attribute source files into your project's intermediate output (
BeforeTargets="CoreCompile") -
An IL weaving task (
WeaveStepsTask) that runs after compilation (AfterTargets="CoreCompile") and modifies the compiled assembly
For each method decorated with a step attribute, the weaver:
- Builds
string[]andobject[]arrays from the method's parameters - Injects a call to
StepCollector.StartStep(keyword, text, paramNames, paramValues)at method entry - Wraps the original method body in a
try/catch:-
Success path: calls
StepCollector.CompleteStep(true, null) -
Exception path: calls
StepCollector.CompleteStep(false, ex.Message)then rethrows
-
Success path: calls
The weaving is guarded against double-application via a sentinel module attribute (__StepTrackingWeaved__).
StepCollector.Options = new StepTrackingOptions
{
PrependKeyword = true, // Include keyword in step display text
InlineParameters = true, // Show parameters inline with step text
WhenTriggersAction = true // Given/But → Setup phase, When/Then → Action phase
};Remove the [assembly: TrackSteps] attribute or uninstall the package. Without the attribute, the weaver skips the assembly entirely.
Step tracking works with all supported test frameworks:
-
xUnit v3 — Steps are collected via
StepCollectorand populated on theScenarioobject - TUnit — Same collection mechanism
- NUnit 4 — Same collection mechanism
-
BDDfy —
StepCollectorsteps are used when BDDfy's own Steps collection is empty (for inline tests) - LightBDD — LightBDD has its own step system; use that instead of this package
The step data flows into the Generated Reports HTML alongside the sequence diagrams.
| Feature | StepTracking | LightBDD | BDDfy |
|---|---|---|---|
| Step definition | Attributes | Attributes + runner | Fluent API + reflection |
| Framework dependency | None (IL weaving) | LightBDD runtime | BDDfy runtime |
| Parameter capture | Automatic (IL weaving) | Framework-provided | Method name convention |
| Phase integration | Automatic | Requires TTD adapter | Requires TTD adapter |
| Assertion sub-steps | With AssertionTracking | Not supported | Not supported |
| Async support | Sync methods only (v2.30.30) | Full async | Full async |
- Sync methods only: The current IL weaver only supports synchronous step methods. Async method support is planned for a future release.
-
No conditional steps: All decorated methods are always instrumented. Use
[Step]withDescriptionfor generic steps that don't fit Given/When/Then. -
Single test assembly: The
[assembly: TrackSteps]attribute must be present in each test assembly that uses step attributes.
- Assertion Tracking — Automatic assertion display in diagrams (can combine with step tracking for assertion sub-steps)
- Phase-Aware Tracking — How Setup/Action phases affect tracking verbosity
- Generated Reports — Where steps appear in the HTML report
- Internal Flow Tracking — Alternative approach for tracking internal method calls
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