Skip to content

Integration ServiceBus Extension

aryehcitron@gmail.com edited this page Nov 7, 2026 · 21 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, so their notes are drawn on the event card.


Install

dotnet add package Kronikol.Extensions.ServiceBus

Client versions

The package asks for Azure.Messaging.ServiceBus 7.11.0 or later, and Microsoft.Extensions.DependencyInjection.Abstractions 10.0.0 or later, so a suite can pin whichever 7.x from 7.11.0 its application ships with. The package's own suite runs on the newest 7.x, and in CI on 7.11.0 as well. A project that takes the client only through this package resolves 7.11.0; reference the client yourself for a newer one. The floor is 7.11.0 because that release added the DeadLetterMessageAsync overload with properties and a reason, which the tracking receiver overrides so that it reaches the receiver it wraps. The DI abstractions' floor is 10.0.0, the first release of the major, where the suite passes; before 5.27.10 it was 10.0.7, which the Kronikol core's Microsoft.AspNetCore.Mvc.Testing asked for.

ServiceBusClient.GetMessageSessionsAsync, new in 7.21.0, is past the floor, so TrackingServiceBusClient cannot override it, and through the tracking client it throws NullReferenceException. Call it on Inner, the client the tracking client wraps; the listing is not drawn.

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 Has no effect: nothing in the Service Bus package reads it (only the HTTP handler's option of the same name does). Its removal is a v6 question
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.

TrackingServiceBusClient hands out the processors, session processors and rule managers of the client it wraps, untracked; disposing it, or a tracking sender or receiver, disposes the one it wraps.


Tracked Sender Operations

TrackingServiceBusSender tracks:

  • SendMessageAsync: logs the message body (at Raw and Detailed)
  • SendMessagesAsync: logs the count, for a list of messages and, from 5.27.3, for a ServiceBusMessageBatch. A batch's messages carry no test identity, as the caller built the batch before the sender saw it, so a receiver in another service cannot tell which test sent them; send a list where that matters
  • ScheduleMessageAsync: logs the message body; the response includes the sequence number
  • CancelScheduledMessageAsync: logs the sequence number

Untracked (pass-through): CreateMessageBatchAsync, CloseAsync, DisposeAsync

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: every overload, with a reason, with properties, or both (the last two from 5.27.3); logs the dead-letter reason (at Raw and Detailed)
  • DeferMessageAsync
  • RenewMessageLockAsync

Untracked (pass-through): CloseAsync, DisposeAsync


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.

A call that fails

A send or a receive that throws is answered ! and the exception's type, with its message chain in error, and keeps the message as its note, none at Summarised (5.34.3, #232); before, it had no status, as a success has none. History's older rules still read the call as the release before recorded it, so a suite that upgrades reads no change there (Cross-Run History).

Home · 🔍 Search this wiki Full-text search across every page. The “Pages” list above matches page and heading names, not page content.


Demo


Getting Started

Common Tasks

Integration Guides

Uninstrumentable / polyglot backends

Extensions

Configuration

Features

Reference

Clone this wiki locally