Skip to content

Step Tracking

Aryeh Citron edited this page May 8, 2026 · 11 revisions

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.


Quick Start

  1. Install the package in your test project:
<PackageReference Include="TestTrackingDiagrams.StepTracking" Version="2.30.30" />
  1. Add the assembly-level opt-in attribute anywhere in your test project:
using TestTrackingDiagrams.Tracking;

[assembly: TrackSteps]
  1. 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, and StepAttribute types are auto-generated into your project at build time — no manual type definitions needed.


Available Attributes

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

Description Override

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".


Keyword Sequencing

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.


Parameter Capture

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 Name Humanization

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:

  1. Replaces underscores with spaces
  2. Splits PascalCase at lowercase→uppercase boundaries
  3. Handles acronym sequences (e.g. "HTTP" stays together until the next word)
  4. Applies sentence case (first letter uppercase, rest lowercase)

Phase Transitions

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.


Assertion Sub-Steps

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.


Nested Steps

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.


How It Works

The TestTrackingDiagrams.StepTracking NuGet package contains:

  1. An MSBuild .targets file that auto-generates the step attribute source files into your project's intermediate output (BeforeTargets="CoreCompile")
  2. 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:

  1. Builds string[] and object[] arrays from the method's parameters
  2. Injects a call to StepCollector.StartStep(keyword, text, paramNames, paramValues) at method entry
  3. 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

The weaving is guarded against double-application via a sentinel module attribute (__StepTrackingWeaved__).


Configuration

StepTrackingOptions

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
};

Disabling Step Tracking

Remove the [assembly: TrackSteps] attribute or uninstall the package. Without the attribute, the weaver skips the assembly entirely.


Framework Compatibility

Step tracking works with all supported test frameworks:

  • xUnit v3 — Steps are collected via StepCollector and populated on the Scenario object
  • TUnit — Same collection mechanism
  • NUnit 4 — Same collection mechanism
  • BDDfyStepCollector steps 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.


Comparison with BDD Frameworks

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

Limitations

  • 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] with Description for 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.

See Also

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally