-
Notifications
You must be signed in to change notification settings - Fork 1
Event Annotations
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):
CallingServiceNamehas been renamed toCallerNameonMessageTrackerOptionsand 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 useMessageTrackerfor HTTP-based dependencies. If the real production interaction between your SUT and a dependency is HTTP, you must route it throughTestTrackingMessageHandlerinstead. UsingMessageTrackerfor 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.).
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.
Added in v2.21.0. This is the recommended approach for new code. It follows the same
Optionspattern used by all other Kronikol extensions and does not requireIHttpContextAccessor.
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.Testcan benullduring 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)
});| 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 |
⚠️ 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) fromHttpContext.Request.Headers(propagated byTestTrackingMessageHandlerthrough the HTTP pipeline), then falls back totestInfoFallbackwhenHttpContextis null.The
MessageTrackerOptions-based registration usesCurrentTestInfoFetcherexclusively by default — it bypassesIHttpContextAccessorentirely. In architectures where messages are published as side-effects of HTTP requests (especially on thread-pool continuations inside the ASP.NET Core pipeline), the xUnitTestContextambient state may not flow to the publishing thread, causing interactions to be silently untracked.If you depend on
IHttpContextAccessor-based correlation, setUseHttpContextCorrelation = truein 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.
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
testInfoFallbackparameter. See Tracking Outside HTTP Request Context below.
Inject MessageTracker into any fake or stub that simulates publishing or sending messages.
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;
}
}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;
}
}| 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). |
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 |
CallerName → destinationName (SUT → broker) |
Request arrow | "Responded" |
TrackConsumeEvent |
CallerName → consumerName (broker → SUT) |
Request arrow |
"Ack" (customisable) |
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
});| 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. |
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)
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:DependencyCategoryis applied to theServiceNameparticipant, NOT toCallerNameor the per-calldestinationName. If yourServiceNameis the SUT (e.g. in consumption), settingDependencyCategory = "MessageQueue"will cause the SUT to render as aqueue. UseCallerDependencyCategoryinstead 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 |
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: SettingCallerNameand passingdestinationNamewith the same value produces a self-loop in the diagram. These must be different service names.
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 = trueand anIHttpContextAccessor. ReturnsfalsewhenHttpContextis 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 comparesIHttpContextAccessorreferences — each DI container has its own singleton accessor instance, so the comparison identifies the hosting container regardless of how theMessageTrackerwas registered.
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).
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 });This section applies to the legacy registration only. When using the recommended
MessageTrackerOptionsregistration, you provideCurrentTestInfoFetcherdirectly in the options — there is no separatetestInfoFallbackparameter. The options-based approach always uses the fetcher delegate, bypassingIHttpContextAccessorentirely.
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 providetestInfoFallback— otherwise those interactions will not appear in the diagrams.
See also: Tracking Dependencies — Gotcha: HTTP Calls During HttpClient Construction for a similar scenario affecting
TestTrackingMessageHandlerwhen HTTP calls occur duringHttpClientconstruction.
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.
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)
| 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
|
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.
When tracking messages through a custom in-memory Service Bus or message broker fake, keep these pitfalls in mind:
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.
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);
}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
}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