Skip to content

Phase Aware Tracking

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

Phase-aware tracking lets you configure tracking behavior differently for the Setup phase (BDD Given/Arrange) vs the Action phase (BDD When-Then/Act-Assert). This is useful for reducing diagram noise — e.g. suppressing verbose database seeding calls during setup while keeping full detail for the action under test.


How It Works

Every tracking extension reads the current test phase from an ambient AsyncLocal context (TestPhaseContext.Current). Based on the phase, each tracker decides:

  1. Whether to track at allTrackDuringSetup / TrackDuringAction (default: both true)
  2. What verbosity to useSetupVerbosity / ActionVerbosity override the default Verbosity during that phase

If no phase is set (the default for non-BDD frameworks that don't call StartSetup()), the phase is Unknown. In this case, the TrackDuringSetup / TrackDuringAction toggles have no effect, and GetEffectiveVerbosity returns the base Verbosity.

However, verbosity overrides still work for non-BDD users via a deferred variant mechanism (see Non-BDD Phase-Aware Verbosity below). When overrides are configured and the phase is Unknown, each tracker captures both a Setup and an Action variant at log time. The diagram renderer then picks the correct variant based on the position of StartAction() — entries before that boundary use the Setup variant, entries after use the Action variant.

Phase Detection

Source How phase is set
BDDfy Automatically from ExecutionOrderSetupState/ConsecutiveSetupState/Assertion → Setup; others → Action
LightBDD Automatically from GetTopLevelStepType() — Given/And/But → Setup; When/Then → Action
ReqNRoll Automatically in BeforeStep hook — Given/And/But → Setup; When/Then → Action
StepTracking Automatically from step attributes — [GivenStep]/[ButStep] → Setup; [WhenStep]/[ThenStep] → Action (see Step Tracking)
Non-BDD Call TrackingDiagramOverride.StartAction() to define the boundary. StartSetup() is also available but optional. Verbosity overrides work via deferred variants when only StartAction() is used.

Quick Start

Suppress all tracking during setup

new SqlTrackingInterceptorOptions
{
    ServiceName = "OrdersDb",
    Verbosity = SqlTrackingVerbosity.Detailed,
    TrackDuringSetup = false  // ← no SQL tracking during Given/Arrange
}

Use summarised verbosity during setup, detailed during action

new SqlTrackingInterceptorOptions
{
    ServiceName = "OrdersDb",
    Verbosity = SqlTrackingVerbosity.Detailed,       // default for Unknown phase
    SetupVerbosity = SqlTrackingVerbosity.Summarised, // less detail during setup
    ActionVerbosity = SqlTrackingVerbosity.Raw         // maximum detail during action
}

Suppress HTTP tracking during setup

services.TrackDependenciesForDiagrams(
    new XUnitTestTrackingMessageHandlerOptions
    {
        CallerName = "My API",
        PortsToServiceNames = { { 80, "My API" } },
        TrackDuringSetup = false  // ← no HTTP tracking during setup
    });

Non-BDD: Explicit phase boundaries

For non-BDD tests, you only need StartAction() to define the boundary between setup and action. Everything before that call is treated as setup; everything after is treated as action.

[Fact]
public async Task Create_order_stores_in_database()
{
    // Setup phase — seeding, config, etc.
    await client.PostAsync("/api/seed-data", seedContent);

    TrackingDiagramOverride.StartAction();

    // Action phase — the actual behavior under test
    var response = await client.PostAsync("/api/orders", orderContent);
    response.StatusCode.Should().Be(HttpStatusCode.Created);
}

You can optionally call StartSetup() first if you want explicit control, but it is not required:

[Fact]
public async Task Create_order_stores_in_database()
{
    TrackingDiagramOverride.StartSetup();

    // Explicit setup phase
    await client.PostAsync("/api/seed-data", seedContent);

    TrackingDiagramOverride.StartAction();

    // Action phase
    var response = await client.PostAsync("/api/orders", orderContent);
    response.StatusCode.Should().Be(HttpStatusCode.Created);
}

BDD frameworks: You don't need to call StartSetup() or StartAction() — phase detection is automatic based on the step keyword.


Non-BDD Phase-Aware Verbosity

v2.27.5+ — Verbosity overrides (SetupVerbosity / ActionVerbosity) now work correctly for non-BDD test frameworks that don't use StartSetup().

How It Works

When the test phase is Unknown (the normal state for non-BDD tests) and at least one verbosity override is configured, each tracker captures two phase variants alongside the main log entry:

  • SetupVariant — the log entry as it would look at SetupVerbosity
  • ActionVariant — the log entry as it would look at ActionVerbosity

The diagram renderer then uses StartAction() as the boundary: entries logged before StartAction() render using their SetupVariant; entries logged after render using their ActionVariant.

This happens automatically — no additional configuration is needed. The only requirement is a call to StartAction() in your test to define the boundary.

Zero Overhead

Variants are only computed when:

  1. The test phase is Unknown at capture time, and
  2. At least one of SetupVerbosity / ActionVerbosity is set

For BDD frameworks (where the phase is always Setup or Action), or when no verbosity overrides are configured, there is zero additional overhead.

Example

// Configure SQL tracking with different verbosity per phase
var options = new SqlTrackingInterceptorOptions
{
    ServiceName = "OrdersDb",
    Verbosity = SqlTrackingVerbosity.Detailed,
    SetupVerbosity = SqlTrackingVerbosity.Other,   // suppress setup SQL from diagram
    ActionVerbosity = SqlTrackingVerbosity.Raw      // full SQL detail during action
};

[Fact]
public async Task Create_order_stores_in_database()
{
    // These SQL calls will use SetupVerbosity (suppressed)
    await SeedTestData();

    TrackingDiagramOverride.StartAction();

    // These SQL calls will use ActionVerbosity (raw detail)
    var order = await CreateOrder();
    order.Should().NotBeNull();
}

Variant Skip Behaviour

If a variant's verbosity resolves to a level that would normally suppress the log entry (e.g. Other for SQL), the variant is marked with Skip = true. The renderer will omit that entry entirely for that phase. This means you can use verbosity to suppress specific log entries in one phase while showing them in another.


Options Reference

All Extension Options

Every extension options class (except OpenTelemetry) supports these four phase-aware properties:

Property Type Default Description
TrackDuringSetup bool true When false, tracking is completely suppressed during the Setup phase
TrackDuringAction bool true When false, tracking is completely suppressed during the Action phase
SetupVerbosity {ExtVerbosity}? null Verbosity override for the Setup phase. null = use default Verbosity
ActionVerbosity {ExtVerbosity}? null Verbosity override for the Action phase. null = use default Verbosity

Where {ExtVerbosity} is the extension-specific verbosity enum (e.g. SqlTrackingVerbosity, RedisTrackingVerbosity, CosmosTrackingVerbosity, etc.).

TestTrackingMessageHandlerOptions (HTTP)

The core HTTP tracking handler supports TrackDuringSetup and TrackDuringAction but does not have verbosity overrides (HTTP tracking doesn't use an enum-based verbosity system):

Property Type Default Description
TrackDuringSetup bool true When false, HTTP requests during Setup are not tracked
TrackDuringAction bool true When false, HTTP requests during Action are not tracked

MediatR Extension

The MediatR extension uses TrackingProxy internally and supports TrackDuringSetup / TrackDuringAction only (no verbosity overrides):

Property Type Default Description
TrackDuringSetup bool true When false, MediatR send/publish tracking is suppressed during Setup
TrackDuringAction bool true When false, MediatR send/publish tracking is suppressed during Action

DispatchProxy Extension

The DispatchProxy extension takes a TrackingProxyOptions directly, which has TrackDuringSetup and TrackDuringAction:

Property Type Default Description
TrackDuringSetup bool true When false, proxy interaction logging is suppressed during Setup
TrackDuringAction bool true When false, proxy interaction logging is suppressed during Action

Extensions Supporting Phase-Aware Verbosity

The following extensions support both the enable/disable toggle (TrackDuringSetup/TrackDuringAction) and per-phase verbosity overrides (SetupVerbosity/ActionVerbosity):

Extension Options Class Verbosity Enum
EF Core (Relational) SqlTrackingInterceptorOptions SqlTrackingVerbosity
Redis RedisTrackingDatabaseOptions RedisTrackingVerbosity
Kafka KafkaTrackingOptions KafkaTrackingVerbosity
gRPC GrpcTrackingOptions GrpcTrackingVerbosity

gRPC note: If your gRPC calls run inside the SUT's request pipeline (SUT → downstream), phase-aware tracking will only work if test identity can be resolved. v2.26.0+: AddTrackedGrpcClient<TClient>() auto-resolves IHttpContextAccessor from DI. For older versions, set HttpContextAccessor on GrpcTrackingOptions manually. See Integration Grpc Extension#Dual-Resolution Test Identity (HttpContextAccessor).

| MassTransit | MassTransitTrackingOptions | MassTransitTrackingVerbosity | | MongoDB | MongoDbTrackingOptions | MongoDbTrackingVerbosity | | CosmosDB | CosmosTrackingMessageHandlerOptions | CosmosTrackingVerbosity | | Elasticsearch | ElasticsearchTrackingOptions | ElasticsearchTrackingVerbosity | | Dapper | DapperTrackingOptions | DapperTrackingVerbosity | | BigQuery | BigQueryTrackingMessageHandlerOptions | BigQueryTrackingVerbosity | | BlobStorage | BlobTrackingMessageHandlerOptions | BlobTrackingVerbosity | | CloudStorage | CloudStorageTrackingMessageHandlerOptions | CloudStorageTrackingVerbosity | | DynamoDB | DynamoDbTrackingMessageHandlerOptions | DynamoDbTrackingVerbosity | | EventBridge | EventBridgeTrackingMessageHandlerOptions | EventBridgeTrackingVerbosity | | S3 | S3TrackingMessageHandlerOptions | S3TrackingVerbosity | | SNS | SnsTrackingMessageHandlerOptions | SnsTrackingVerbosity | | SQS | SqsTrackingMessageHandlerOptions | SqsTrackingVerbosity | | StorageQueues | StorageQueueTrackingMessageHandlerOptions | StorageQueueTrackingVerbosity | | EventHubs | EventHubsTrackingOptions | EventHubsTrackingVerbosity | | PubSub | PubSubTrackingOptions | PubSubTrackingVerbosity | | ServiceBus | ServiceBusTrackingOptions | ServiceBusTrackingVerbosity |


Combining with Setup Separation

Phase-aware tracking works independently from Diagram Customisation#Setup Separation (SeparateSetup = true). They complement each other:

Feature Purpose
Setup Separation Visual — wraps setup calls in a coloured partition in the diagram
Phase-Aware Tracking Functional — controls whether calls are tracked and at what verbosity

You can use both together: suppress noisy setup database calls (TrackDuringSetup = false on the DB extension) while still showing setup HTTP calls in a partition (SeparateSetup = true).


RequestResponseLog Phase Property

Each RequestResponseLog entry now includes a Phase property (TestPhase enum) indicating which phase produced it. This is available in:

  • Custom report generators that consume RequestResponseLog directly
  • The JSON/YAML/XML export formats

How Phase Detection Works Internally

Test Step Executes
    │
    ├── BDD adapter (BDDfy/LightBDD/ReqNRoll)
    │   └── Calls PhaseConfiguration.ResolvePhaseFromStepType("Given")
    │       └── Sets TestPhaseContext.Current = Setup
    │
    ├── OR: Non-BDD test
    │   └── Optionally calls TrackingDiagramOverride.StartSetup()
    │   └── Calls TrackingDiagramOverride.StartAction()
    │       └── Sets TestPhaseContext.Current = Action
    │       └── Inserts IsActionStart marker into log queue
    │
    ▼
Tracker intercepts operation (SQL, Redis, HTTP, etc.)
    │
    ├── PhaseConfiguration.ShouldTrack(trackDuringSetup, trackDuringAction)
    │   └── Reads TestPhaseContext.Current
    │   └── Returns false → skip tracking entirely
    │
    ├── PhaseConfiguration.GetEffectiveVerbosity(default, setupOverride, actionOverride)
    │   └── Reads TestPhaseContext.Current
    │   └── Known phase → returns phase-appropriate verbosity
    │   └── Unknown phase → returns default verbosity
    │
    ├── [If phase is Unknown AND overrides configured]
    │   └── AttachVariants() computes SetupVariant + ActionVariant
    │       └── Each variant captures method, URI, content, headers, skip flag
    │       └── Variant verbosity determines content shaping and skip
    │
    └── Logs with Phase = TestPhaseContext.Current on RequestResponseLog
    
    ▼
Renderer (PlantUmlCreator) processes log entries
    │
    ├── Tracks isInActionPhase (starts false, set true at IsActionStart)
    │
    ├── If entry has variants:
    │   └── Before IsActionStart → use SetupVariant
    │   └── After IsActionStart → use ActionVariant
    │   └── If active variant has Skip = true → omit entry
    │
    └── Renders with effective method, URI, content, headers

Step Type to Phase Mapping

Step Keyword Phase
Given, And, But Setup
When, Then Action
null / unrecognised Unknown

Note: And and But always map to Setup because in BDD conventions they typically continue the Given context. If your And/But steps follow a When or Then, you can override by calling TrackingDiagramOverride.StartAction() explicitly.


Not Supported

  • OpenTelemetry Extension: The OpenTelemetry extension is a pass-through that captures spans from the OpenTelemetry SDK. It does not have its own tracking loop, so phase-aware configuration does not apply.

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally