-
Notifications
You must be signed in to change notification settings - Fork 1
Service Bus Tracking Patterns
This page covers patterns for tracking Service Bus message publishing and consumption in diagrams.
Azure SDK Service Bus? If your code uses the Azure
ServiceBusClientSDK directly, useKronikol.Extensions.ServiceBus— see Integration ServiceBus Extension. This page covers custom patterns for platform abstractions and in-memory messaging.
Service Bus tracking uses the MessageTracker class registered via TrackMessagesForDiagrams(). There are two common patterns:
| Pattern | Use When |
|---|---|
TrackSendEvent() |
You want event-styled arrows (light blue, "Responded" label) — standard for fire-and-forget publishes |
TrackMessageRequest() + TrackMessageResponse()
|
You want standard-styled arrows (white, status labels) — useful when the publish is a synchronous side effect of an HTTP request |
See Event & Message Tracking#When to Use MessageTracker vs TestTrackingMessageHandler for a visual comparison of the arrow styles.
⚠ WebApplicationFactory threading model: When your SUT publishes a message during request processing, the publishing code runs on the server's thread pool — not the test thread.
TestContext.Currentisnullon that thread, soCurrentTestInfo.Fetcheralone cannot resolve test identity. SettingUseHttpContextCorrelation = trueis essential — it reads test identity from HTTP request headers propagated byTestTrackingMessageHandler. See Background Thread Correlation#scenario-resolution-reference for the full resolution chain and scenario reference table.
services.TrackMessagesForDiagrams(new MessageTrackerOptions
{
CallerName = "My Service",
ServiceName = "Service Bus",
UseHttpContextCorrelation = true, // Required for server-side message tracking
CurrentTestInfoFetcher = CurrentTestInfo.Fetcher,
});| Property | Description |
|---|---|
CallerName |
The service publishing the message (shown as the source in diagrams) |
ServiceName |
The message broker (typically "Service Bus") |
UseHttpContextCorrelation |
Required for WebApplicationFactory scenarios. Reads tracking headers from HttpContext to correlate async messages back to the originating test |
CurrentTestInfoFetcher |
Fallback when HttpContext is not available (e.g. timer-triggered or standalone background publishing). See Background Thread Correlation#scenario-resolution-reference for when each resolution level is used |
For messaging abstractions that expose BeforePublish/AfterPublish events, bridge them to MessageTracker:
public class ServiceBusMessageTrackingHandler
{
private readonly MessageTracker _tracker;
private readonly AsyncLocal<Guid> _pendingCorrelationId = new();
public ServiceBusMessageTrackingHandler(MessageTracker tracker) =>
_tracker = tracker;
public void Attach(IMessageSender sender)
{
sender.BeforePublish += OnBeforePublish;
sender.AfterPublish += OnAfterPublish;
}
private void OnBeforePublish(object? sender, PublishMessageArgs args)
{
var destination = args.QueueOrTopic ?? "unknown";
var correlationId = _tracker.TrackMessageRequest(
protocol: "Send (Service Bus)",
destinationName: "Service Bus",
destinationUri: new Uri($"servicebus://service-bus/{destination}"),
payload: args.Message ?? new { });
_pendingCorrelationId.Value = correlationId;
}
private void OnAfterPublish(object? sender, PublishMessageArgs args)
{
var correlationId = _pendingCorrelationId.Value;
if (correlationId == Guid.Empty) return;
var destination = args.QueueOrTopic ?? "unknown";
_tracker.TrackMessageResponse(
protocol: "Send (Service Bus)",
destinationName: "Service Bus",
destinationUri: new Uri($"servicebus://service-bus/{destination}"),
requestResponseId: correlationId);
_pendingCorrelationId.Value = Guid.Empty;
}
}Why
AsyncLocal<Guid>? TheBeforePublishandAfterPublishevents fire synchronously around theSendMessagecall, but multiple tests may send messages concurrently.AsyncLocalensures each async flow gets its own correlation ID.
Why check for
Guid.Empty? IfBeforePublishisn't fired for certain message types,AfterPublishshould be a no-op rather than logging an orphaned response.
If you prefer event-styled arrows and want simpler code, track only in AfterPublish:
public void Attach(IMessageSender sender)
{
sender.AfterPublish += (_, args) =>
{
try
{
var destination = args.QueueOrTopic ?? "unknown";
_tracker.TrackSendEvent(
protocol: "Publish (Service Bus)",
destinationName: "Service Bus",
destinationUri: new Uri($"servicebus://service-bus/{destination}"),
payload: args.Message);
}
catch
{
// Tracking is best-effort — never fail a test due to tracking
}
};
}// MessageTracker is registered in the API host by TrackMessagesForDiagrams()
var messageTracker = WebFactory.Services.GetService<MessageTracker>();
if (messageTracker is not null)
{
var handler = new ServiceBusMessageTrackingHandler(messageTracker);
handler.Attach(messageSender);
}Timing: The tracker must be wired after both the API host and any Function host are initialised. See Multi-Host Test Architectures#Cross-Container Tracker Bridging.
A common pitfall is tracking the request in BeforePublish and the response in AfterPublish separately:
// ❌ WRONG — if publish throws, AfterPublish never fires → unpaired request in diagnostics
private void OnBeforePublish(object? sender, PublishMessageArgs args)
{
RequestResponseLogger.Log(/* request entry */);
}
private void OnAfterPublish(object? sender, PublishMessageArgs args)
{
RequestResponseLogger.Log(/* response entry */); // Never called if publish throws
}The fix: Use MessageTracker.TrackSendEvent() (logs both atomically) or stash args in BeforePublish and track both entries in AfterPublish as shown above.
In Given/When/Then tests, setup messages should appear as coming from "Caller" while action messages should appear as coming from the service. Use an AsyncLocal-based scope:
public class ServiceBusMessageTrackingHandler
{
private readonly MessageTracker _serviceTracker;
private readonly MessageTracker _callerTracker;
private readonly AsyncLocal<MessageTracker?> _activeTracker = new();
public ServiceBusMessageTrackingHandler(MessageTracker serviceTracker)
{
_serviceTracker = serviceTracker;
_callerTracker = new MessageTracker(new MessageTrackerOptions
{
CallerName = "Caller",
ServiceName = "Service Bus",
CurrentTestInfoFetcher = serviceTracker.Options.CurrentTestInfoFetcher,
});
}
/// <summary>
/// Returns a scope that attributes subsequent messages to "Caller" instead of the service.
/// </summary>
public IDisposable AsExternalCaller() => new CallerScope(this);
private MessageTracker ActiveTracker => _activeTracker.Value ?? _serviceTracker;
private sealed class CallerScope(ServiceBusMessageTrackingHandler handler) : IDisposable
{
public CallerScope(ServiceBusMessageTrackingHandler handler) : this(handler)
=> handler._activeTracker.Value = handler._callerTracker;
public void Dispose() => handler._activeTracker.Value = null;
}
}Usage during test setup:
using (trackingHandler.AsExternalCaller())
{
await SeedTestData(); // Messages attributed to "Caller"
}
// After Dispose, messages are attributed to "My Service"For tracking when a function or handler consumes a message, use TrackConsumeEvent():
messageTracker.TrackConsumeEvent(
protocol: "Consume (Service Bus)",
consumerName: "Service Bus",
sourceUri: new Uri($"servicebus://service-bus/{queueOrTopic}"),
payload: message);See Event & Message Tracking#Tracking Message Consumption (TrackConsumeEvent) for full details, including cross-host duplicate guards.
When a test triggers an Azure Function via HTTP POST, the Function's HttpContext has no tracking headers — so Cosmos/HTTP operations inside the Function are attributed to "Unknown".
Fix: Include test tracking headers on the trigger request:
var request = new HttpRequestMessage(HttpMethod.Post, "/api/functions/MyTrigger")
{
Content = new StringContent(string.Empty)
};
var (testName, testId) = CurrentTestInfo.Fetcher();
request.Headers.Add("test-tracking-current-test-name", testName);
request.Headers.Add("test-tracking-current-test-id", testId);
request.Headers.Add("test-tracking-trace-id", Guid.NewGuid().ToString());
await functionClient.SendAsync(request);Alternatively, use TestIdentityScope.Begin() — see Background Thread Correlation.
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