Skip to content

Event Annotations

aryehcitron@gmail.com edited this page May 24, 2026 · 25 revisions

Requests can be marked with RequestResponseMetaType.Event to give them special styling in diagrams. Event-annotated notes are rendered with:

  • A light blue background (#cfecf7)
  • Smaller font size (11px)
  • Rounded corners

Deprecation (v2.28.1): CallingServiceName has been renamed to CallerName on MessageTrackerOptions and all extension options classes. The old property still works but produces a compile warning.

Testing event-driven architectures? If your SUT is triggered entirely by consuming messages from a broker (no direct HTTP calls from the test), see Event-Driven Architecture Testing for the complete end-to-end pattern including identity propagation, parallel execution, and troubleshooting.

This is useful for distinguishing genuinely asynchronous or non-HTTP interactions — such as Kafka events, EventGrid notifications, RabbitMQ messages, SNS/SQS, or webhooks — from standard HTTP request/response flows.

⚠️ Important: Do NOT use MessageTracker for HTTP-based dependencies. If the real production interaction between your SUT and a dependency is HTTP, you must route it through TestTrackingMessageHandler instead. Using MessageTracker for HTTP calls produces event-style diagram arrows (blue notes, no HTTP method, no status code, no headers) that are misleading and inconsistent with the rest of the diagram. See Tracking Dependencies#faking-dependencies-getting-proper-http-tracking for the correct approaches to faking HTTP dependencies (WireMock, JustEat HttpClient Interception, in-memory fake APIs, etc.).

MessageTracker

The MessageTracker class makes it easy to log non-HTTP interactions — such as events, messages, or commands sent via Kafka, EventGrid, RabbitMQ, SNS, or any other transport — so they appear in the generated sequence diagrams alongside regular HTTP traffic.

Outbox pattern? If your service publishes events through an outbox (e.g. writing to Cosmos DB and dispatching via a background service), you need to track at the outbox write point — not the background dispatcher. See Tracking Custom Dependencies#tracking-outbox-event-publishing for a complete guide with UseHttpContextCorrelation = true.

MessageTracker implements ITrackingComponent, so it automatically registers with the TrackingComponentRegistry and participates in unused-component diagnostics. If you register a MessageTracker but no messages are tracked during a test, the report will flag it as unused.

Registration (Recommended)

Added in v2.21.0. This is the recommended approach for new code. It follows the same Options pattern used by all other Kronikol extensions and does not require IHttpContextAccessor.

Register MessageTracker using MessageTrackerOptions in your WebApplicationFactory startup:

xUnit 3:

builder.ConfigureTestServices(services =>
{
    services.TrackDependenciesForDiagrams(new XUnitTestTrackingMessageHandlerOptions { ... });
    services.TrackMessagesForDiagrams(new MessageTrackerOptions
    {
        CallerName = "My API",
        ServiceName = "Kafka",   // appears in diagram participant name & component diagnostics
        CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
    });
});

Null-safety: TestContext.Current.Test can be null during app startup or DI container warm-up. Always use a null-safe delegate. See HTTP Tracking Setup — Startup HTTP Calls for details.

xUnit 2:

services.TrackMessagesForDiagrams(new MessageTrackerOptions
{
    CallerName = "My API",
    CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
});

TUnit:

services.TrackMessagesForDiagrams(new MessageTrackerOptions
{
    CallerName = "My API",
    CurrentTestInfoFetcher = () => (
        TestContext.Current!.Metadata.DisplayName,
        TestContext.Current.Id)
});

MessageTrackerOptions Properties

Property Default Description
ServiceName "MessageBus" The participant name for the messaging service. Appears in diagram labels and ComponentName for diagnostics ("MessageTracker (Kafka)").
CallerName "Caller" The participant name for the service sending messages.
CallerDependencyCategory null The dependency category for the CallerName participant. Controls the PlantUML shape of the caller independently of DependencyCategory. For example, set to "MessageQueue" to render a broker caller as a queue. See DependencyCategory Reference below.
Verbosity Detailed Controls how much detail is logged. See Verbosity below.
CurrentTestInfoFetcher null Delegate returning the current test name and ID. Required — when null, tracking calls are silently skipped. When the delegate throws (e.g. outside test context), the exception is caught and the call is silently skipped. All framework CurrentTestInfo.Fetcher implementations are null-safe (v2.27.10+).
CurrentStepTypeFetcher null Optional BDD step type fetcher for framework integration.
SerializerOptions null JsonSerializerOptions for serialising payloads. Uses default options when null.
UseHttpContextCorrelation false When true, reads test info from IHttpContextAccessor request headers first (same dual-layer correlation as the legacy constructor), falling back to CurrentTestInfoFetcher when HttpContext is null. See Migrating from Legacy Registration below.
DependencyCategory "MessageQueue" Dependency category for participant shape/colour in diagrams
SetupVerbosity null Verbosity override for the Setup phase (MessageTrackerVerbosity?). See Phase-Aware Tracking
ActionVerbosity null Verbosity override for the Action phase (MessageTrackerVerbosity?). See Phase-Aware Tracking
TrackDuringSetup true When false, tracking is suppressed during Setup
TrackDuringAction true When false, tracking is suppressed during Action

Migrating from Legacy Registration

⚠️ Migration Warning: The legacy constructor uses a dual-layer correlation model: it first reads test-tracking headers (test-tracking-current-test-name, test-tracking-current-test-id) from HttpContext.Request.Headers (propagated by TestTrackingMessageHandler through the HTTP pipeline), then falls back to testInfoFallback when HttpContext is null.

The MessageTrackerOptions-based registration uses CurrentTestInfoFetcher exclusively by default — it bypasses IHttpContextAccessor entirely. In architectures where messages are published as side-effects of HTTP requests (especially on thread-pool continuations inside the ASP.NET Core pipeline), the xUnit TestContext ambient state may not flow to the publishing thread, causing interactions to be silently untracked.

If you depend on IHttpContextAccessor-based correlation, set UseHttpContextCorrelation = true in your options. This enables the same dual-layer resolution used by the legacy constructor:

services.TrackMessagesForDiagrams(new MessageTrackerOptions
{
    CallerName = "My API",
    UseHttpContextCorrelation = true,
    CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
});

Before migrating, compare your report's interaction counts against a baseline. If you see a drop in messaging interactions, enable UseHttpContextCorrelation.

Registration (Legacy)

The legacy registration reads test info from IHttpContextAccessor headers, with an optional testInfoFallback delegate. This approach is still fully supported and backward-compatible.

builder.ConfigureTestServices(services =>
{
    services.TrackDependenciesForDiagrams(new XUnitTestTrackingMessageHandlerOptions { ... });
    services.TrackMessagesForDiagrams(callingServiceName: "My API");
});

This registers MessageTracker as a singleton in DI, along with IHttpContextAccessor (needed to read test-tracking headers from the current request context). When using this approach, the ServiceName defaults to "MessageBus" and verbosity defaults to Detailed.

If your tests trigger behaviour via non-HTTP entry points (service bus messages, background jobs, etc.), you should also pass the testInfoFallback parameter. See Tracking Outside HTTP Request Context below.

Usage

Inject MessageTracker into any fake or stub that simulates publishing or sending messages.

Fire-and-Forget With TrackSendEvent (Recommended)

Added in v2.21.0. For simple fire-and-forget event publishing, TrackSendEvent() logs both the request and response in a single call:

public class FakeEventPublisher : IEventPublisher
{
    private readonly MessageTracker _tracker;

    public FakeEventPublisher(MessageTracker tracker)
    {
        _tracker = tracker;
    }

    public Task PublishAsync(OrderCreatedEvent @event)
    {
        _tracker.TrackSendEvent(
            protocol: "Send (Event Protocol)",
            destinationName: "Order Service",
            destinationUri: new Uri("event://order-service/order_events"),
            payload: @event);

        return Task.CompletedTask;
    }
}

Request/Response Pair (Full Control)

When you need fine-grained control — for example, to log a response payload or handle multi-step flows — use the TrackMessageRequest / TrackMessageResponse pair:

public class FakeEventPublisher : IEventPublisher
{
    private readonly MessageTracker _tracker;

    public FakeEventPublisher(MessageTracker tracker)
    {
        _tracker = tracker;
    }

    public Task PublishAsync(OrderCreatedEvent @event)
    {
        var correlationId = _tracker.TrackMessageRequest(
            protocol: "Send (Event Protocol)",
            destinationName: "Order Service",
            destinationUri: new Uri("event://order-service/order_events"),
            payload: @event);

        _tracker.TrackMessageResponse(
            protocol: "Send (Event Protocol)",
            destinationName: "Order Service",
            destinationUri: new Uri("event://order-service/order_events"),
            requestResponseId: correlationId);

        return Task.CompletedTask;
    }
}

Parameters

Parameter Description
protocol The transport label shown in the diagram (e.g. "Send (Event Protocol)", "Kafka", "SNS", "RabbitMQ").
destinationName The name of the destination service or topic shown in the diagram.
destinationUri A URI representing the destination (e.g. new Uri("event://order-service/order_events")). The path portion appears in the diagram arrow label.
payload The message payload — serialised to JSON and shown in the diagram note.
requestResponseId The correlation ID returned by TrackMessageRequest, used to pair the request and response.
responsePayload Optional response payload for TrackMessageResponse (e.g. an acknowledgement).

Tracking Message Consumption (TrackConsumeEvent)

Added in v2.27.19.

TrackConsumeEvent() models the "broker delivers an event to the SUT" pattern — producing a correctly directed arrow (broker → consumer) with the event payload on the delivery arrow and a clean acknowledgement on the return.

_tracker.TrackConsumeEvent(
    protocol: "Consume (Kafka)",
    consumerName: "Breakfast Provider",
    sourceUri: new Uri("kafka:///breakfast_recipe_logs"),
    payload: message);

This is the mirror image of TrackSendEvent():

Method Arrow Direction Payload On Response Label
TrackSendEvent CallerNamedestinationName (SUT → broker) Request arrow "Responded"
TrackConsumeEvent CallerNameconsumerName (broker → SUT) Request arrow "Ack" (customisable)

Registration for Consumption

When tracking consumption, set CallerName to the broker (arrow source) and use CallerDependencyCategory to give it the correct shape:

services.TrackMessagesForDiagrams(new MessageTrackerOptions
{
    CallerName = "Kafka Broker",         // Arrow source (broker delivers)
    ServiceName = "Kafka Broker",                // For component diagnostics
    DependencyCategory = "",                      // SUT stays as entity (not queue)
    CallerDependencyCategory = "MessageQueue",   // Broker gets queue shape
    UseHttpContextCorrelation = true,
    CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
});

TrackConsumeEvent Parameters

Parameter Default Description
protocol Transport label (e.g. "Consume (Kafka)", "Consume (Pub/Sub)").
consumerName The consuming service name (arrow target).
sourceUri URI of the topic/subscription (e.g. new Uri("kafka:///my_topic")).
payload null The consumed event payload. Serialised to JSON in the diagram note.
ackLabel "Ack" The label for the acknowledgement (return) arrow.

Arrow Direction Convention

Publishing arrows go SUT → broker (outward). Consumption arrows go broker → SUT (inward):

Publishing (TrackSendEvent):
  SUT ──────────── payload ──────────►  Broker
  SUT ◄──────────────────────────────  Broker  (Responded)

Consumption (TrackConsumeEvent):
  Broker ────────── payload ─────────►  SUT
  Broker ◄──────────────────────────  SUT     (Ack)

DependencyCategory Reference

The DependencyCategory property on MessageTrackerOptions determines the PlantUML participant shape for the ServiceName participant. The CallerDependencyCategory property controls the shape of the CallerName participant.

⚠️ Important: DependencyCategory is applied to the ServiceName participant, NOT to CallerName or the per-call destinationName. If your ServiceName is the SUT (e.g. in consumption), setting DependencyCategory = "MessageQueue" will cause the SUT to render as a queue. Use CallerDependencyCategory instead when the broker is the caller.

DependencyCategory Value DependencyType PlantUML Shape Colour When to Use
"MessageQueue" MessageQueue queue #9B59B6 (purple) Kafka, RabbitMQ, SQS, etc.
"ServiceBus" MessageQueue queue #9B59B6 (purple) Azure Service Bus
"Database" Database database #E74C3C (red) SQL Server, PostgreSQL, etc.
"CosmosDB" Database database #E74C3C (red) Azure Cosmos DB
"MongoDB" Database database #E74C3C (red) MongoDB
"DynamoDB" Database database #E74C3C (red) AWS DynamoDB
"Elasticsearch" Database database #E74C3C (red) Elasticsearch
"Spanner" Database database #E74C3C (red) Google Cloud Spanner
"Bigtable" Database database #E74C3C (red) Google Cloud Bigtable
"SQL" Database database #E74C3C (red) SQL operations
"BigQuery" Database database #E74C3C (red) Google BigQuery
"Redis" Cache collections #F39C12 (orange) Redis, Memcached
"S3" Storage database #2ECC71 (green) AWS S3
"CloudStorage" Storage database #2ECC71 (green) GCS, Azure Blob
"BlobStorage" Storage database #2ECC71 (green) Azure Blob Storage
"HTTP" HttpApi entity #438DD5 (blue) REST APIs, microservices
"gRPC" HttpApi entity #438DD5 (blue) gRPC services
"MediatR" HttpApi entity #438DD5 (blue) MediatR handlers
"" or null HttpApi entity #438DD5 (blue) Default / fallback

Participant Naming and Arrow Direction

The MessageTracker has three "name" concepts that control arrow direction and participant rendering:

              CallerName              destinationName / consumerName
                     │                               │
                     ▼                               ▼
                ┌─────────┐    protocol: ...    ┌─────────┐
                │ Source   │ ──────────────────► │  Dest   │
                │ (left)   │                    │ (right)  │
                └─────────┘ ◄────────────────── └─────────┘
                                 Responded / Ack

ServiceName: used for ComponentName display and DependencyCategory association.
             NOT used for arrow direction. Usually same as destinationName.
Rule Guideline
CallerName must differ from destinationName/consumerName Same value = self-loop in diagram
DependencyCategory applies to ServiceName participant Use CallerDependencyCategory for the CallerName participant
For publishing: CallerName = SUT, destinationName = broker SUT calls outward to broker
For consumption: CallerName = broker, consumerName = SUT Broker delivers inward to SUT

⚠️ Common Mistake: Setting CallerName and passing destinationName with the same value produces a self-loop in the diagram. These must be different service names.

Cross-Host Duplicate Consume Guard (IsCurrentRequestFromMyHost)

Added in v2.27.19.

When multiple WebApplicationFactory instances share a global in-memory message store, each factory's consumer service subscribes to the same events. This causes duplicate tracking — every consumer fires for every message, even if the producing request belongs to a different factory.

IsCurrentRequestFromMyHost() checks whether the current HttpContext belongs to the same DI container that created this MessageTracker instance:

public class InMemoryKafkaConsumerService(
    ConsumedKafkaMessageStore store,
    MessageTracker tracker) : IHostedService
{
    private void HandleMessage(string eventType, string key, string json)
    {
        if (!tracker.IsCurrentRequestFromMyHost()) return;  // Skip if not our host

        tracker.TrackConsumeEvent(
            protocol: "Consume (Kafka)",
            consumerName: "My API",
            sourceUri: new Uri("kafka:///my_topic"),
            payload: JsonSerializer.Deserialize<MyEvent>(json));
    }
}

Requirements: The tracker must have been created with UseHttpContextCorrelation = true and an IHttpContextAccessor. Returns false when HttpContext is null or when the tracker is from a different DI container.

Keyed DI support (v2.27.20+): The method works correctly with keyed singletons (AddKeyedSingleton("Kafka", ...)), non-keyed singletons, and manually constructed trackers. Internally it compares IHttpContextAccessor references — each DI container has its own singleton accessor instance, so the comparison identifies the hosting container regardless of how the MessageTracker was registered.

Verbosity

Added in v2.21.0.

Control the amount of detail logged for each message via MessageTrackerVerbosity:

Level Behaviour
Raw Serialises payloads exactly as provided — no transformation.
Detailed (Default) Same as Raw — serialises payloads to JSON.
Summarised Omits all payload content. Useful when messages are large or contain sensitive data, and you only want to see the interaction arrows without body content.

Set the verbosity via MessageTrackerOptions:

services.TrackMessagesForDiagrams(new MessageTrackerOptions
{
    CallerName = "My API",
    Verbosity = MessageTrackerVerbosity.Summarised,
    CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
});

Or via the legacy registration (which always uses Detailed verbosity — change is not supported with this overload).

Custom JSON Serialisation

If your payloads require specific serialisation settings, pass JsonSerializerOptions via the options object:

services.TrackMessagesForDiagrams(new MessageTrackerOptions
{
    CallerName = "My API",
    SerializerOptions = new JsonSerializerOptions { WriteIndented = true },
    CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
});

The legacy registration also supports this:

services.TrackMessagesForDiagrams(
    callingServiceName: "My API",
    serializerOptions: new JsonSerializerOptions { WriteIndented = true });

Tracking Outside HTTP Request Context (testInfoFallback)

This section applies to the legacy registration only. When using the recommended MessageTrackerOptions registration, you provide CurrentTestInfoFetcher directly in the options — there is no separate testInfoFallback parameter. The options-based approach always uses the fetcher delegate, bypassing IHttpContextAccessor entirely.

When using the legacy registration, MessageTracker reads test-tracking headers from IHttpContextAccessor.HttpContext to determine which test a tracked message belongs to. This works when the message is published during HTTP request processing (e.g. your API receives a request and publishes an event as a side-effect).

However, if a message is published outside of an HTTP request context — e.g. triggered by a service bus message, during hosted service startup, background task processing, or test fixture setup — HttpContext will be null and there are no test-tracking headers to read.

This is a common scenario in Function-based or event-driven architectures where tests send service bus messages directly (not via HTTP), and the function handler may publish outbound messages or interact with other services. Without configuration, these interactions will silently fail to be tracked, resulting in missing diagrams.

To handle this, pass a testInfoFallback delegate when registering MessageTracker. This delegate is called whenever HttpContext is unavailable:

xUnit 3:

builder.ConfigureTestServices(services =>
{
    services.TrackDependenciesForDiagrams(new XUnitTestTrackingMessageHandlerOptions { ... });
    services.TrackMessagesForDiagrams(
        callingServiceName: "My API",
        testInfoFallback: CurrentTestInfo.Fetcher);
});

xUnit 2:

using Kronikol.Tracking;
using Kronikol.xUnit2;

services.TrackMessagesForDiagrams(
    callingServiceName: "My API",
    testInfoFallback: CurrentTestInfo.Fetcher);

TUnit:

using TUnit.Core;

services.TrackMessagesForDiagrams(
    callingServiceName: "My API",
    testInfoFallback: () => (
        TestContext.Current!.Metadata.DisplayName,
        TestContext.Current.Id));

ReqNRoll + TUnit:

using Kronikol.ReqNRoll.TUnit;

services.TrackMessagesForDiagrams(
    callingServiceName: "My API",
    testInfoFallback: () => ReqNRollTestContext.CurrentTestInfo
        ?? throw new InvalidOperationException("No ReqNRoll scenario is currently executing."));

Resolution order: MessageTracker first attempts to read headers from HttpContext. If HttpContext is null (or the test-tracking headers are missing), it falls back to the testInfoFallback delegate. If neither is available, it throws an InvalidOperationException with a descriptive message.

When is this needed? If your tests trigger behaviour via non-HTTP entry points (service bus messages, background jobs, timers) and the handler publishes messages or calls downstream services that are tracked via MessageTracker, you must provide testInfoFallback — otherwise those interactions will not appear in the diagrams.

See also: Tracking Dependencies — Gotcha: HTTP Calls During HttpClient Construction for a similar scenario affecting TestTrackingMessageHandler when HTTP calls occur during HttpClient construction.


When to Use MessageTracker vs TestTrackingMessageHandler

This is one of the most common sources of confusion. The two mechanisms produce visually different outputs in your diagrams, and using the wrong one leads to misleading documentation.

Visual Comparison

HTTP tracking (via TestTrackingMessageHandler) produces:

caller -> scvApi: GET: /v2/identifiers?ssoId=sub
note left                          ← white background, standard note
[Accept=application/json]

end note
scvApi --> caller: 200 OK
note right                         ← white background, status code visible
[Content-Type=application/json]

{ "customerId": "custNbr123" }
end note
  • Arrow label shows the HTTP method (GET:, POST:, etc.)
  • Response shows the HTTP status code (200 OK, 404 Not Found, etc.)
  • Request and response headers are captured
  • Request and response bodies are captured
  • Notes have a white background (standard styling)

Event tracking (via MessageTracker) produces:

caller -> scvApi: HTTP: /v2/identifiers?ssoId=sub
note<<eventNote>> left             ← BLUE background, rounded corners
"sub"
end note
scvApi --> caller: Responded       ← no status code
  • Arrow label shows the protocol string you passed (e.g. "HTTP", "Kafka", "SNS")
  • Response always shows "Responded" — no HTTP status code
  • No headers are captured
  • Only the payload you explicitly pass appears in the note
  • Notes have a light blue background with rounded corners (event styling)

Decision Guide

The real production interaction is... Use Why
HTTP (REST API, GraphQL over HTTP, gRPC-web) TestTrackingMessageHandler Captures full HTTP semantics (method, status, headers, body)
Cosmos DB CosmosTrackingMessageHandler Pre-built extension; uses RequestResponseLogger.Log() internally
EF Core / relational database SqlTrackingInterceptor Pre-built extension; intercepts DbCommand
Redis (StackExchange.Redis) RedisTrackingDatabase Pre-built extension; decorates IDatabase
Kafka event / message MessageTracker No HTTP involved; protocol is Kafka
RabbitMQ / SNS / SQS message MessageTracker No HTTP involved; protocol is the message broker
EventGrid / Azure Service Bus notification MessageTracker Asynchronous messaging, not request/response HTTP
Synchronous SDK fake (Blob Storage, Key Vault, etc.) RequestResponseLogger.Log() No HTTP pipeline; creates standard call-and-return arrows. See Tracking Custom Dependencies
Webhook (your SUT sends an HTTP call to a callback URL) TestTrackingMessageHandler It's still HTTP — track it properly through the pipeline
gRPC (over HTTP/2) GrpcTrackingInterceptor / GrpcTrackingChannel.Create() Pre-built extension; deserializes protobuf, classifies operations, produces grpc:// URIs. See Integration Grpc Extension

Common Mistake: Wrapping Mocks with MessageTracker

A common pattern that leads to incorrect diagrams is mocking a service client interface and then wrapping the mock to manually log calls via MessageTracker:

// ❌ WRONG — produces event-style (blue) arrows for what is actually an HTTP call
public class TrackingScvProvider : IScvProvider
{
    private readonly IScvProvider _mock;       // NSubstitute mock
    private readonly MessageTracker _tracker;

    public async Task<ScvResponse> GetIdentifiersAsync(string ssoId)
    {
        var id = _tracker.TrackMessageRequest(
            protocol: "HTTP",
            destinationName: "SCV API",
            destinationUri: new Uri($"http://scv/v2/identifiers?ssoId=%3Cspan%20class="pl-kos">{ssoId}"),
            payload: ssoId);

        var result = await _mock.GetIdentifiersAsync(ssoId);

        _tracker.TrackMessageResponse(
            protocol: "HTTP",
            destinationName: "SCV API",
            destinationUri: new Uri("http://scv/v2/identifiers"),
            requestResponseId: id);

        return result;
    }
}

The fix: Replace the mock with a real HTTP fake (WireMock, JustEat HttpClient Interception, or an in-memory API) and let TestTrackingMessageHandler capture the call automatically. See Tracking Dependencies#faking-dependencies-getting-proper-http-tracking for detailed examples of each approach.


Tips for Tracking In-Memory Message Brokers

When tracking messages through a custom in-memory Service Bus or message broker fake, keep these pitfalls in mind:

Hook at the Broker Level, Not the Sender Level

Many in-memory fakes route messages through multiple paths — e.g. SendMessageAsync() on a sender vs EnqueueMessage() / ScheduleMessage() on the client or broker. If you only hook tracking into the sender, messages published internally by the SUT (routed directly through the broker) will silently not appear in diagrams.

Track where messages actually flow through — typically the client or broker's send/enqueue/schedule methods — not just the sender wrapper.

Avoid Copying MessageTracker at Construction Time

If your fake ServiceBusClient creates senders/receivers before WebApplicationFactory.Services is built, MessageTracker will be null at sender construction time. Senders that capture the tracker in a field at construction will never get the real instance.

Instead, read the tracker from the parent client at send time:

// ❌ Breaks if MessageTracker is set after sender creation
internal MessageTracker? MessageTracker { get; set; }  // copied from client at construction

// ✅ Always reads current value from parent
private void TrackMessage(ServiceBusMessage message)
{
    _client.MessageTracker?.TrackMessageRequest(
        protocol: "Service Bus",
        destinationName: "Service Bus",
        destinationUri: new Uri($"sb://servicebus/{_queueName}"),
        payload: message.Body);
}

Wrap Tracking Calls in try/catch

Tracking failures should never break test execution. For example, XUnit2TestTrackingContext.GetCurrentTestInfo() returns a fallback ("Unknown Test", ...) when no test is active, but your own code around the tracker (e.g. accessing test-specific state) might throw during fixture setup or teardown.

try
{
    _client.MessageTracker?.TrackMessageRequest(...);
}
catch
{
    // Tracking is best-effort — don't break the test
}

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally