
As I wrote last week, I finally got on the AI bus and started using Claude Code. One of the things I’ve been doing with that so far is ripping through “chore” tasks that I’ve long wanted to do, but sounded too time consuming. One of those things was converting Wolverine’s own test suite for its Azure Service Bus integration into using the Azure Service Bus Emulator — which turned out to be extremely fortunate timing as the emulator recently gained support for the Azure Service Bus Management API that finally made the emulator usable.
The emulator is already turning out to be very useful for Wolverine development, especially in areas where we needed 3-5 namespaces just to test features like “namespace per tenant” and named broker features inside of Wolverine. A JasperFx client just happened to ask about that this week, so I lazily had Claude build a new section in the Wolverine docs to explain how that’s working for us and how you might use the emulator for your own local testing. Maybe in the next Wolverine (5.17) we’ll add some syntactical sugar to make this a little easier.
In the meantime, here’s how we’re using the emulator for Wolverine testing:
The Azure Service Bus Emulator allows you to run integration tests against a local emulator instance instead of a real Azure Service Bus namespace. This is exactly what Wolverine uses internally for its own test suite.
Docker Compose Setup
The Azure Service Bus Emulator requires a SQL Server backend. Here is a minimal Docker Compose setup:
networks: sb-emulator:services: asb-sql: image: "mcr.microsoft.com/azure-sql-edge" environment: - "ACCEPT_EULA=Y" - "MSSQL_SA_PASSWORD=Strong_Passw0rd#2025" networks: sb-emulator: asb-emulator: image: "mcr.microsoft.com/azure-messaging/servicebus-emulator:latest" volumes: - ./docker/asb/Config.json:/ServiceBus_Emulator/ConfigFiles/Config.json ports: - "5673:5672" # AMQP messaging - "5300:5300" # HTTP management environment: SQL_SERVER: asb-sql MSSQL_SA_PASSWORD: "Strong_Passw0rd#2025" ACCEPT_EULA: "Y" EMULATOR_HTTP_PORT: 5300 depends_on: - asb-sql networks: sb-emulator:
TIP
The emulator exposes two ports: the AMQP port (5672) for sending and receiving messages, and an HTTP management port (5300) for queue/topic administration. These must be mapped to different host ports.
Emulator Configuration File
The emulator reads a Config.json file on startup. A minimal configuration that lets Wolverine auto-provision everything it needs:
{ "UserConfig": { "Namespaces": [ { "Name": "sbemulatorns" } ], "Logging": { "Type": "File" } }}
You can also pre-configure queues and topics in this file if needed:
{ "UserConfig": { "Namespaces": [ { "Name": "sbemulatorns", "Queues": [ { "Name": "my-queue", "Properties": { "MaxDeliveryCount": 3, "LockDuration": "PT1M", "RequiresSession": false } } ], "Topics": [ { "Name": "my-topic", "Subscriptions": [ { "Name": "my-subscription", "Properties": { "MaxDeliveryCount": 3, "LockDuration": "PT1M" } } ] } ] } ], "Logging": { "Type": "File" } }}
Connection Strings
The emulator uses standard Azure Service Bus connection strings with UseDevelopmentEmulator=true:
// AMQP connection for sending/receiving messagesvar messagingConnectionString = "Endpoint=sb://localhost:5673;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;";// HTTP connection for management operations (creating queues, topics, etc.)var managementConnectionString = "Endpoint=sb://localhost:5300;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;";
WARNING
The emulator uses separate ports for messaging (AMQP) and management (HTTP) operations. In production Azure Service Bus, a single connection string handles both, but the emulator requires you to configure these separately.
Configuring Wolverine with the Emulator
The key to using the emulator with Wolverine is setting both the primary connection string (for AMQP messaging) and the ManagementConnectionString (for HTTP administration) on the transport:
var builder = Host.CreateApplicationBuilder();builder.UseWolverine(opts =>{ opts.UseAzureServiceBus(messagingConnectionString) .AutoProvision() .AutoPurgeOnStartup(); // Required for the emulator: set the management connection string // to the HTTP port since it differs from the AMQP port var transport = opts.Transports.GetOrCreate<AzureServiceBusTransport>(); transport.ManagementConnectionString = managementConnectionString; // Configure your queues, topics, etc. as normal opts.ListenToAzureServiceBusQueue("my-queue"); opts.PublishAllMessages().ToAzureServiceBusQueue("my-queue");});
Creating a Test Helper
Wolverine’s own test suite uses a static helper extension method to standardize emulator configuration across all tests. Here’s the pattern:
public static class AzureServiceBusTesting{ // Connection strings pointing at the emulator public static readonly string MessagingConnectionString = "Endpoint=sb://localhost:5673;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"; public static readonly string ManagementConnectionString = "Endpoint=sb://localhost:5300;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"; private static bool _cleaned; public static AzureServiceBusConfiguration UseAzureServiceBusTesting( this WolverineOptions options) { // Delete all queues and topics on first usage to start clean if (!_cleaned) { _cleaned = true; DeleteAllEmulatorObjectsAsync().GetAwaiter().GetResult(); } var config = options.UseAzureServiceBus(MessagingConnectionString); var transport = options.Transports.GetOrCreate<AzureServiceBusTransport>(); transport.ManagementConnectionString = ManagementConnectionString; return config.AutoProvision(); } public static async Task DeleteAllEmulatorObjectsAsync() { var client = new ServiceBusAdministrationClient(ManagementConnectionString); await foreach (var topic in client.GetTopicsAsync()) { await client.DeleteTopicAsync(topic.Name); } await foreach (var queue in client.GetQueuesAsync()) { await client.DeleteQueueAsync(queue.Name); } }}
Writing Integration Tests
With the helper in place, integration tests become straightforward:
public class when_sending_messages : IAsyncLifetime{ private IHost _host; public async Task InitializeAsync() { _host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { opts.UseAzureServiceBusTesting() .AutoPurgeOnStartup(); opts.ListenToAzureServiceBusQueue("send_and_receive"); opts.PublishMessage<MyMessage>() .ToAzureServiceBusQueue("send_and_receive"); }).StartAsync(); } public async Task DisposeAsync() { await _host.StopAsync(); } [Fact] public async Task send_and_receive_a_single_message() { var message = new MyMessage("Hello"); var session = await _host.TrackActivity() .IncludeExternalTransports() .Timeout(30.Seconds()) .SendMessageAndWaitAsync(message); session.Received.SingleMessage<MyMessage>() .Name.ShouldBe("Hello"); }}
TIP
Use .IncludeExternalTransports() on the tracked session so Wolverine waits for messages that travel through Azure Service Bus rather than only tracking in-memory activity.
Disabling Parallel Test Execution
Because the emulator is a shared resource, tests that create and tear down queues or topics can interfere with each other when run in parallel. Wolverine’s own test suite disables parallel execution for its Azure Service Bus tests:
// Add to a file like NoParallelization.cs in your test project[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]