Skip to content

Integration ServiceBus Extension

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

The Kronikol.Extensions.ServiceBus package adds Azure Service Bus messaging operation tracking to your test diagrams. Instead of opaque AMQP traffic, your sequence diagrams show classified operations like Send → orders-queue or Receive ← notifications-topic.

Using a shared library or abstraction layer? If your code doesn't use the ServiceBusClient SDK directly — e.g. it goes through MassTransit, NServiceBus, a shared messaging library, or a custom abstraction — see the MassTransit Extension or Tracking Custom Dependencies for alternative approaches including MessageTracker and TrackingProxy<T>.


How It Works

The Azure Service Bus SDK (Azure.Messaging.ServiceBus) uses AMQP by default, which does not go through an HttpMessageHandler pipeline. This extension uses a wrapper/decorator pattern instead — TrackingServiceBusClient wraps the real ServiceBusClient and returns tracked senders (TrackingServiceBusSender) and receivers (TrackingServiceBusReceiver) that intercept every operation and log it to RequestResponseLogger.

Because it logs to the same RequestResponseLogger as the standard TestTrackingMessageHandler, Service Bus operations appear alongside your HTTP API calls in the same sequence diagram — showing the complete flow from test → API → Service Bus.

Messaging operations (Send, Receive, Schedule, Peek) are tagged with MetaType.Event for blue async-messaging rendering in PlantUML diagrams.


Install

dotnet add package Kronikol.Extensions.ServiceBus

Verbosity Levels

The extension supports three verbosity levels that control how much detail appears in the diagrams:

Level Label shown URI shown Message body
Raw Enum name (Send, SendBatch) servicebus://{queue} Included
Detailed Operation with arrows (Send → orders-queue, Receive ← orders-queue) servicebus://{queue} or servicebus://{topic}/{subscription} Included
Summarised Simple name (Send, Receive, Complete) servicebus://{queue}/ None

The default is Detailed.

Diagram Label Examples

Operation Raw Detailed Summarised
Send a message Send Send → orders-queue Send
Send a batch (5 messages) SendBatch Send (×5) → orders-queue Send
Schedule a message Schedule Schedule → orders-queue Schedule
Receive a message Receive Receive ← orders-queue Receive
Receive a batch (10 messages) ReceiveBatch Receive (×10) ← orders-queue Receive
Peek Peek Peek ← orders-queue Peek
Complete Complete Complete Complete
Abandon Abandon Abandon Abandon
Dead-letter DeadLetter DeadLetter DeadLetter
Defer Defer Defer Defer
Renew lock RenewMessageLock RenewLock RenewLock

Classified Operations

The classifier recognises these Service Bus operations:

Operation Method Name
Send SendMessageAsync
SendBatch SendMessagesAsync
Schedule ScheduleMessageAsync, ScheduleMessagesAsync
CancelSchedule CancelScheduledMessageAsync, CancelScheduledMessagesAsync
Receive ReceiveMessageAsync
ReceiveBatch ReceiveMessagesAsync
Peek PeekMessageAsync, PeekMessagesAsync
Complete CompleteMessageAsync
Abandon AbandonMessageAsync
DeadLetter DeadLetterMessageAsync
Defer DeferMessageAsync
RenewMessageLock RenewMessageLockAsync
RenewSessionLock RenewSessionLockAsync
GetSessionState GetSessionStateAsync
SetSessionState SetSessionStateAsync
StartProcessing StartProcessingAsync
StopProcessing StopProcessingAsync

Setup

Option A: DI Decoration (Recommended — Zero Prod Changes)

v2.27.14+ Use the built-in DI extension method. It automatically resolves IHttpContextAccessor from DI and handles the dual-resolution test identity pattern.

// In your test's ConfigureTestServices:
services.AddServiceBusTestTracking(options =>
{
    options.ServiceName = "ServiceBus";
    options.CallerName = "OrdersApi";
    options.Verbosity = ServiceBusTrackingVerbosity.Detailed;
    options.CurrentTestInfoFetcher = CurrentTestInfo.Fetcher;
});

This uses DecorateAll<ServiceBusClient> internally — it finds all existing ServiceBusClient registrations, replaces each with a TrackingServiceBusClient (which inherits from ServiceBusClient), and preserves the original service lifetime. No-op if no ServiceBusClient registrations exist. No changes to production code required.

v2.27.14 Breaking Change: TrackingServiceBusClient now inherits from ServiceBusClient (previously a composition wrapper). The Inner property and existing API remain available, but the service is now registered as ServiceBusClient (not as TrackingServiceBusClient). Code that resolves ServiceBusClient from DI will receive the tracking subclass transparently.

Option B: With TrackingServiceBusClient

Create a TrackingServiceBusClient that wraps your existing ServiceBusClient:

var trackingOptions = new ServiceBusTrackingOptions
{
    ServiceName = "ServiceBus",
    CallerName = "My API",
    Verbosity = ServiceBusTrackingVerbosity.Detailed,
    CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
};

var realClient = new ServiceBusClient("your-connection-string");
var trackedClient = new TrackingServiceBusClient(realClient, trackingOptions);

// Use trackedClient instead of realClient
var sender = trackedClient.CreateSender("orders-queue");
await sender.SendMessageAsync(new ServiceBusMessage("hello"));

var receiver = trackedClient.CreateReceiver("orders-queue");
var message = await receiver.ReceiveMessageAsync();

Option C: Legacy DI (pre-v2.27.14)

Use AddServiceBusTestTracking() with a pre-constructed options object:

// In your test WebApplicationFactory or DI setup:
services.AddServiceBusTestTracking(new ServiceBusTrackingOptions
{
    ServiceName = "ServiceBus",
    CallerName = "OrdersApi",
    Verbosity = ServiceBusTrackingVerbosity.Detailed,
    CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
});

This overload is preserved for backward compatibility and uses the same DecorateAll<ServiceBusClient> pattern internally.


Configuration Reference

ServiceBusTrackingOptions

Property Type Default Description
ServiceName string "ServiceBus" Display name in diagrams for the Service Bus
CallerName string "Caller" Calling service name in diagrams
Verbosity ServiceBusTrackingVerbosity Detailed Verbosity level (Raw, Detailed, Summarised)
CurrentTestInfoFetcher Func<(string Name, string Id)>? null Required: provides test context for log correlation
CurrentStepTypeFetcher Func<string?>? null Optional — returns the current BDD step type (Given/When/Then)
HttpContextAccessor IHttpContextAccessor? null Optional — enables dual-resolution of test identity from HTTP headers. Auto-resolved by DI extensions (v2.26.3+). See HTTP Tracking Setup#Dual-Resolution Test Identity (v2.23.0+)
SetupVerbosity ServiceBusTrackingVerbosity? null Verbosity override for the Setup phase. See Phase-Aware Tracking
ActionVerbosity ServiceBusTrackingVerbosity? null Verbosity override for the Action phase. See Phase-Aware Tracking
TrackDuringSetup bool true When false, tracking is suppressed during Setup. See Phase-Aware Tracking
TrackDuringAction bool true When false, tracking is suppressed during Action. See Phase-Aware Tracking
PropagateTestIdentity bool true When true, senders inject kronikol-test-name / kronikol-test-id into ApplicationProperties and receivers extract them to establish TestIdentityScope. See Background Thread Correlation#Solution 1b: Automatic Message Header Propagation (v2.34.0+)
AutoCorrelateOnConsume bool true When true, received messages auto-populate TestCorrelationStore for parallel-safe background thread correlation

v2.23.0+ Dual-Resolution: ServiceBusTracker accepts an optional IHttpContextAccessor? httpContextAccessor constructor parameter for resolving test identity from HTTP request headers when running inside the SUT's request pipeline. v2.26.3+: Set HttpContextAccessor on ServiceBusTrackingOptions instead — the tracker reads it automatically. DI extensions like AddServiceBusTestTracking() auto-resolve it from the service provider. See HTTP Tracking Setup#Dual-Resolution Test Identity (v2.23.0+) for details.


Wrapper Architecture

Because ServiceBusSender and ServiceBusReceiver are sealed classes in the Azure SDK, this extension uses wrapper classes rather than DelegatingHandler or DispatchProxy:

ServiceBusClient (real)
  └── TrackingServiceBusClient (subclass, extends ServiceBusClient)
       ├── CreateSender() → TrackingServiceBusSender (wraps ServiceBusSender)
       └── CreateReceiver() → TrackingServiceBusReceiver (wraps ServiceBusReceiver)

Each wrapper delegates all calls to the real instance and adds tracking before/after each operation. The Inner property on each wrapper provides escape-hatch access to the original SDK object.


Tracked Sender Operations

TrackingServiceBusSender tracks:

  • SendMessageAsync — logs message body (at Raw/Detailed)
  • SendMessagesAsync — logs count
  • ScheduleMessageAsync — logs message body, response includes sequence number
  • CancelScheduledMessageAsync — logs sequence number

Untracked (pass-through): CreateMessageBatchAsync, CloseAsync

Tracked Receiver Operations

TrackingServiceBusReceiver tracks:

  • ReceiveMessageAsync — logs received message body in response
  • ReceiveMessagesAsync — logs count in response
  • PeekMessageAsync — logs peeked message body in response
  • CompleteMessageAsync
  • AbandonMessageAsync
  • DeadLetterMessageAsync — logs dead-letter reason (at Raw/Detailed)
  • DeferMessageAsync
  • RenewMessageLockAsync

Untracked (pass-through): CloseAsync


Invocation Tracking & Diagnostics

ServiceBusTracker implements ITrackingComponent and auto-registers with TrackingComponentRegistry on construction. If the tracker is registered but never processes any operations, a passive warning appears in the console output and (when DiagnosticMode=true) in the HTML diagnostic report. This never throws or fails tests — see Diagnostics and Debugging for details.


Test Identity Propagation (v2.34.0+)

When your application processes Service Bus messages on background threads (e.g. inside a ServiceBusProcessor or hosted service), the test framework's TestContext is unavailable. The extension solves this automatically via ApplicationProperties propagation.

How It Works

  1. Sender side: TrackingServiceBusSender injects kronikol-test-name and kronikol-test-id into each message's ApplicationProperties before sending.
  2. Receiver side: TrackingServiceBusReceiver reads these properties from each received message and calls TestIdentityScope.SetFromMessage().
  3. All subsequent tracking within the message handler resolves to the originating test.

Disabling Propagation

var options = new ServiceBusTrackingOptions
{
    PropagateTestIdentity = false,
    // ...
};

See Background Thread Correlation#Solution 1b: Automatic Message Header Propagation (v2.34.0+) for the full architecture overview.

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally