OpenServiceBus.Testing
1.0.2
See the version list below for details.
dotnet add package OpenServiceBus.Testing --version 1.0.2
NuGet\Install-Package OpenServiceBus.Testing -Version 1.0.2
<PackageReference Include="OpenServiceBus.Testing" Version="1.0.2" />
<PackageVersion Include="OpenServiceBus.Testing" Version="1.0.2" />
<PackageReference Include="OpenServiceBus.Testing" />
paket add OpenServiceBus.Testing --version 1.0.2
#r "nuget: OpenServiceBus.Testing, 1.0.2"
#:package OpenServiceBus.Testing@1.0.2
#addin nuget:?package=OpenServiceBus.Testing&version=1.0.2
#tool nuget:?package=OpenServiceBus.Testing&version=1.0.2
OpenServiceBus
A zero-dependency, embeddable Azure Service Bus emulator for .NET. Speaks real AMQP 1.0 over TCP and WebSocket so
Azure.Messaging.ServiceBus, Azure FunctionsServiceBusTrigger, and any other Service Bus client connect unmodified.
Why this exists
Microsoft ships an official Service Bus emulator, but it needs Docker + SQL Server and is
EULA-gated. OpenServiceBus is the MIT-licensed alternative: a single-node Service Bus
emulator that runs as a NuGet inside your test fixture, as a docker run, or as a
standalone executable — no SQL Server, no licensing dance, no 5-minute container boot.
It implements the full Service Bus feature surface that real client code uses: queues, topics + subscriptions (SQL/correlation filters), sessions, duplicate detection, auto-forwarding, transactions, defer, scheduled messages, TTL, peek-lock with renewal, dead-letter routing, exposed over both plain AMQP and AMQP-over-WebSocket, with native OpenTelemetry instrumentation throughout.
| OpenServiceBus | Official Microsoft emulator | |
|---|---|---|
| License | MIT | EULA-gated |
| Embeddable in tests | ✅ OpenServiceBus.Testing NuGet |
❌ |
| Container size | ~230 MB Alpine | ~2 GB |
| Persistence | In-memory or SQLite (single file) | SQL Server (required) |
| Transports | AMQP-TCP + AMQP-over-WebSocket | AMQP-TCP only |
| Telemetry | Native OpenTelemetry tracing + metrics | None |
config.json compatible |
✅ | ✅ |
Install
From source
git clone https://github.com/mauritsarissen/OpenServiceBus
cd OpenServiceBus
dotnet run --project src/OpenServiceBus.Host
The host binds amqp://localhost:5672 (Service Bus SDK) and http://localhost:5300
(REST management + /health).
Docker
docker run -d --name openservicebus \
-p 5672:5672 \
-p 5300:5300 \
-p 5400:5400 \
-v osb-data:/data \
mauritsarissen/openservicebus:latest
| Port | What |
|---|---|
5672 |
AMQP (use this in the Azure SDK connection string) |
5300 |
REST management API + /health |
5400 |
Explorer browser UI — open http://localhost:5400 |
5673 |
AMQP-over-WebSocket (when OPENSERVICEBUS__WEBSOCKETS__ENABLED=true) |
The image runs the broker and the Explorer UI side-by-side. SQLite-backed at
/data/broker.db — mount the named volume and queues + messages survive container
recreates. See Docker for the compose recipe, every env var, and the
WebSocket-transport setup.
Connect
The same connection string works against either install path:
Endpoint=sb://localhost:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true
await using var client = new ServiceBusClient(
"Endpoint=sb://localhost:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true");
await client.CreateSender("orders").SendMessageAsync(new ServiceBusMessage("hello"));
var msg = await client.CreateReceiver("orders").ReceiveMessageAsync();
UseDevelopmentEmulator=true tells the Azure SDK to skip TLS and accept the broker on
plain TCP — the same trick the official Microsoft emulator uses.
As a NuGet inside your tests
If you don't want any external process at all, install the test fixture and run the broker inside your test process:
dotnet add package OpenServiceBus.Testing
Published on nuget.org — no extra source needed. (GitHub Packages also mirrors every release, see Contributing.)
await using var host = await OpenServiceBusTestHost.StartAsync();
await host.CreateQueueAsync("orders");
await using var client = new ServiceBusClient(host.ConnectionString);
await client.CreateSender("orders").SendMessageAsync(new ServiceBusMessage("hello"));
var receiver = client.CreateReceiver("orders");
var msg = await receiver.ReceiveMessageAsync();
await receiver.CompleteMessageAsync(msg);
One disposable host, free ephemeral port, full AMQP semantics — run thousands of these in parallel. See Testing for time-travel, parity testing, and the options surface.
Feature recipes
Topics + filters (SQL / correlation / true / false)
await host.Topics.CreateTopicAsync(new TopicDescriptor { Name = "events" });
await host.Topics.CreateSubscriptionAsync(new SubscriptionDescriptor
{ TopicName = "events", Name = "eu" });
await host.Topics.CreateOrReplaceRuleAsync(new RuleDescriptor
{
TopicName = "events", SubscriptionName = "eu", Name = "EuOnly",
Filter = new SqlFilter("region = 'eu' AND priority >= 5"),
});
await client.CreateSender("events").SendMessageAsync(new ServiceBusMessage("hi-eu")
{
ApplicationProperties = { ["region"] = "eu", ["priority"] = 7 }
});
Sessions
await host.Queues.CreateAsync(new QueueDescriptor { Name = "sessioned", RequiresSession = true });
var sender = client.CreateSender("sessioned");
await sender.SendMessageAsync(new ServiceBusMessage("a") { SessionId = "S" });
await sender.SendMessageAsync(new ServiceBusMessage("b") { SessionId = "S" });
var session = await client.AcceptSessionAsync("sessioned", "S");
var first = await session.ReceiveMessageAsync(); // "a"
var second = await session.ReceiveMessageAsync(); // "b"
→ Sessions
Transactions (TransactionScope)
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendMessageAsync(new ServiceBusMessage("a"));
await receiver.CompleteMessageAsync(previouslyReceivedMessage);
scope.Complete(); // commit; without this everything rolls back
}
Auto-forwarding
await host.Queues.CreateAsync(new QueueDescriptor { Name = "downstream" });
await host.Queues.CreateAsync(new QueueDescriptor
{ Name = "ingress", ForwardTo = "downstream" });
// Sends to "ingress" land on "downstream" — invisible to senders.
Duplicate detection
await host.Queues.CreateAsync(new QueueDescriptor
{
Name = "deduped",
RequiresDuplicateDetection = true,
DuplicateDetectionHistoryTimeWindow = TimeSpan.FromMinutes(5),
});
await sender.SendMessageAsync(new ServiceBusMessage("first") { MessageId = "k" });
await sender.SendMessageAsync(new ServiceBusMessage("second") { MessageId = "k" }); // silently dropped
Declarative config.json bootstrap
OpenServiceBus reads the same config.json format as the official Microsoft emulator:
{
"UserConfig": {
"Namespaces": [{
"Name": "demo",
"Queues": [
{ "Name": "orders", "Properties": {
"LockDuration": "PT1M",
"MaxDeliveryCount": 3,
"RequiresSession": false,
"DefaultMessageTimeToLive": "PT1H"
}}
]
}]
}
}
Resolution: --config <path> → OPENSERVICEBUS_CONFIG env var → config.json in the
host's content-root. Full schema in Configuration.
Persistence (SQLite)
export OPENSERVICEBUS__STORAGE__MODE=Sqlite
export OPENSERVICEBUS__STORAGE__DATASOURCE=/data/broker.db
dotnet run --project src/OpenServiceBus.Host
AMQP-over-WebSocket
export OPENSERVICEBUS__WEBSOCKETS__ENABLED=true
dotnet run --project src/OpenServiceBus.Host
await using var client = new ServiceBusClient(
"Endpoint=sb://localhost:5673;...;UseDevelopmentEmulator=true",
new ServiceBusClientOptions { TransportType = ServiceBusTransportType.AmqpWebSockets });
OpenTelemetry
services.AddOpenTelemetry()
.WithTracing(b => b.AddSource(OpenServiceBusDiagnostics.SourceName).AddOtlpExporter())
.WithMetrics(b => b.AddMeter(OpenServiceBusDiagnostics.SourceName).AddOtlpExporter());
Configuration
The host honors these appsettings.json / environment variable keys:
| Key | Default | Notes |
|---|---|---|
OpenServiceBus:Amqp:Port |
5672 |
AMQP listener port |
OpenServiceBus:Amqp:RequireSasAuth |
false |
Validate $cbs put-token against SasKeys |
OpenServiceBus:Storage:Mode |
InMemory |
Sqlite to persist via the SQLite store |
OpenServiceBus:Storage:DataSource |
:memory: |
Path to the SQLite .db file (use /data/broker.db in containers) |
OpenServiceBus:WebSockets:Enabled |
false |
Start the AMQP-over-WebSocket bridge |
OpenServiceBus:WebSockets:Port |
5673 |
WebSocket bridge port |
OPENSERVICEBUS_CONFIG |
— | Path to a config.json for declarative bootstrap |
Full reference: Configuration.
Samples
Each sample is self-contained: it ships a docker-compose.yml, a config.json, and a
README.md that explains what it demonstrates and how to run it.
samples/OpenServiceBus.Samples.QuickStart— minimal console send/receive against an emulator container.samples/OpenServiceBus.Samples.TopicsAndFilters— pub-sub with SQL + correlation filter rules.samples/OpenServiceBus.Samples.Sessions— session-locked workers with per-session FIFO ordering.samples/OpenServiceBus.Samples.WorkerService—Microsoft.Extensions.Hostingbackground-worker pattern.samples/OpenServiceBus.Samples.Functions— minimal Azure FunctionsServiceBusTriggerapp (the M11 integration-test target).samples/OpenServiceBus.Samples.FunctionsTriggerDemo— interactive multi-trigger Functions app driven via the Explorer or HTTP.
See samples/README.md for the full index and a quick chooser table.
Documentation
Multi-page guides in docs/ — also published to the GitHub Wiki:
- Getting Started — install + first send/receive
- Configuration —
config.json, env vars, every option - Architecture — assemblies, AMQP layer, store contracts
- Docker — image, compose, env vars, persistence
- Persistence — SQLite store, schema, restart semantics
- Topics and Subscriptions — filters, rules, fan-out
- Sessions — session-locked receivers, state, ordering
- Auto-Forwarding —
ForwardTo, DLQ forwarding, cycles - Transactions — coordinator,
TransactionScope, semantics - Duplicate Detection — window, observed behavior
- WebSocket Transport — bridge setup, ports, client config
- OpenTelemetry — source names, attributes, gauges
- Explorer UI — running the browser console
- Testing —
OpenServiceBusTestHost, fake-time, parity tests - Contributing — repo layout, conventions, releasing
- Roadmap — what's next
Development
dotnet build # multi-targets net8.0 + net10.0
dotnet test # full regression suite (~210 tests)
docker build -t openservicebus:dev .
The Azure Functions integration test (M11) requires func (Azure Functions Core Tools v4)
and the .NET 8 runtime; it skips when either is missing. See Contributing
for repo conventions, the release flow, and how to add a new milestone.
License
MIT © Maurits Arissen and OpenServiceBus contributors.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AMQPNetLite (>= 2.4.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- OpenServiceBus.Amqp (>= 1.0.2)
- OpenServiceBus.InMemoryStorage (>= 1.0.2)
-
net8.0
- AMQPNetLite (>= 2.4.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- OpenServiceBus.Amqp (>= 1.0.2)
- OpenServiceBus.InMemoryStorage (>= 1.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.