Azure Service Bus emulator vs the cloud: the differences that bite in tests
By Factodus · updated
The Azure Service Bus emulator runs Service Bus locally in Docker, so tests and local development don’t need a cloud namespace. It speaks the same AMQP protocol and works with the normal SDKs. It is still a different piece of software with its own quotas and gaps. Most of them are documented. A few we only found while testing our own tool against emulator image 2.0. This page collects both, so a test that passes locally doesn’t surprise you in Azure, or the other way round.
Short version. One namespace, 50 queues and topics, 256 KB messages, a time to live of at most an hour, no Microsoft Entra ID, no partitioned entities and nothing persists across a container restart. The management API is plain HTTP on port 5300 and is supported natively only by the .NET administration client. Counts from the management API aren’t reliable: subscription counts stay at zero. A plain receiver on a session-enabled entity silently gets nothing.
How it runs
The emulator is two containers. One is mcr.microsoft.com/azure-messaging/servicebus-emulator, the other is SQL
Server Linux, which the emulator uses as storage. AMQP is on port 5672. Management and a health check
(http://localhost:5300/health) are on port 5300. Entities come from a config.json mounted into the container.
Changes to that file need a restart, and at start-up the file overrides anything you created through the
administration client.
The connection string is fixed. For the runtime (sending and receiving):
Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;
For the administration client, the same string with the management port:
Endpoint=sb://localhost:5300;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;
From another container, replace localhost with the emulator’s container name on the same bridge network, or with
host.docker.internal.
Quotas
These are Microsoft’s published limits for the emulator. Some can be raised in the configuration, but not above these values:
| What | Emulator | Configurable |
|---|---|---|
| Namespaces | 1 | No |
| Queues and topics per namespace | 50 | Yes |
| Subscriptions per topic | 50 | Yes |
| Message size | 256 KB | No |
| Queue or topic size | 100 MB | No |
| Message time to live | 1 hour | Yes |
| Concurrent connections to the namespace | 10 | Yes |
| SQL filters per topic | 70 | Yes |
The one-hour time to live matters more than it looks. A test that relies on a message surviving longer, or on a default time to live copied from production, behaves differently locally.
Features the emulator doesn’t have
- Microsoft Entra ID. Only the shared key in the connection string works, so code paths using
DefaultAzureCredentialcan’t be tested against it. - Partitioned entities, JMS, and AMQP over WebSockets (only AMQP over TCP).
- Large messages, geo-disaster recovery, autoscale, virtual networks, metrics and alerts, and a portal UI.
- Persistence: entities and messages are gone after a container restart.
- The community Service Bus Explorer desktop app. Microsoft states it isn’t compatible with the emulator.
What we ran into
These come from running an extension’s integration tests against emulator image 2.0 with the JavaScript SDK. They aren’t in Microsoft’s list of differences.
The management API is plain HTTP. Port 5300 serves management over HTTP, not HTTPS. Microsoft says that managing entities through the administration client is supported natively only in .NET. The JavaScript administration client insists on HTTPS, so we had to switch the scheme ourselves.
Counts are not reliable. Queue descriptions from the management API carry no count details, and subscription
counts are always zero. Don’t assert on activeMessageCount or deadLetterMessageCount in emulator tests. Peek the
entity and count what comes back.
Updates succeed, but the answer can’t be parsed. Updating an entity’s properties works on the emulator, but the JavaScript SDK fails to parse the response. Read the entity back and compare the settings instead of trusting the update call.
A session-enabled entity looks empty to a plain receiver. On Azure, a plain receiver on a session-enabled queue
fails. On the emulator it simply receives or peeks nothing. If a local test sees an empty queue that should have
messages, check RequiresSession and use a session receiver.
Making tests behave the same in both places
- Wait for
http://localhost:5300/healthbefore the first test. The emulator starts after its SQL container. - Create the entities a test needs in
config.json, or from .NET through the administration client. Assume nothing survives a restart. - Count messages by peeking, not through the management API.
- Keep messages under 256 KB and time to live under an hour, or run those tests against a real namespace.
- Keep Entra ID tests separate: they need Azure.
- Microsoft describes the emulator as meant for sequential tests. Heavy parallelism also runs into the 10-connection limit.
From VS Code
Queue Studio for Service Bus (our extension) connects to the emulator with its host, AMQP port and management port. Its tree shows emulator counts computed from a peek walk, because the emulator’s own counts are unreliable. Session-enabled queues are peeked one session at a time. Browsing and sending are free, which is handy because the emulator has no portal of its own.