Testing
Testing event-driven applications can be tricky because producing and consuming messages is asynchronous and typically involves external infrastructure (Kafka, MQTT, ...).
Silverback ships dedicated testing packages that replace the broker connectivity with in-memory mocked brokers and exposes a set of helpers to make end-to-end testing deterministic.
Packages
Choose the package matching the broker you’re using:
- Silverback.Integration.Kafka.Testing
- Mocked in-memory Kafka broker (topics, consumer groups, commits, ...).
- Registers IKafkaTestingHelper.
- Silverback.Integration.MQTT.Testing
- Mocked in-memory MQTT broker.
- Registers IMqttTestingHelper.
- Silverback.Integration.Testing
- Shared testing infrastructure (e.g. IIntegrationSpy and ITestingHelper).
- This package is referenced by the broker-specific testing packages.
Two Ways to Use the Mocked Broker
There are two supported approaches and they solve different problems:
Test-only configuration: your test registers the broker using
AddMockedKafka/AddMockedMqtt. This is the simplest approach when your test builds theServiceCollection(or you fully control the test host setup).Override an existing configuration: your application configures a real broker in
Startup/Program, and your test host overrides that by callingUseMockedKafka/UseMockedMqttinConfigureTestServices(or equivalent). This is ideal when the app’s configuration is shared between production and tests, and you just want to swap the transport.
Tip
You don’t need both. AddMockedKafka and AddMockedMqtt already register the broker and replace the transport.
UseMockedKafka / UseMockedMqtt are for overriding an existing setup.
Approach 1: Test-Only Configuration (Recommended When Building the DI Container)
Kafka (AddMockedKafka)
services
.AddSilverback()
.WithConnectionToMessageBroker(options => options.AddMockedKafka())
.AddKafkaClients(
clients => clients
.WithBootstrapServers("PLAINTEXT://tests")
.AddProducer(
producer => producer
.Produce<SomeMessage>(endpoint => endpoint.ProduceTo("test-topic")))
.AddConsumer(
consumer => consumer
.WithGroupId("test-consumer")
.Consume(endpoint => endpoint.ConsumeFrom("test-topic"))));
MQTT (AddMockedMqtt)
services
.AddSilverback()
.WithConnectionToMessageBroker(options => options.AddMockedMqtt())
.AddMqttClients(
clients => clients
.AddProducer(
producer => producer
.Produce<SomeMessage>(endpoint => endpoint.ProduceTo("test/topic")))
.AddConsumer(
consumer => consumer
.Consume(endpoint => endpoint.ConsumeFrom("test/topic"))));
Approach 2: Override an Existing Configuration (WebApplicationFactory / Test Host)
If your application always configures the real broker (e.g. AddKafka() / AddMqtt() in Program.cs),
then in tests you can keep that configuration and only swap the connectivity implementation.
Kafka (UseMockedKafka)
builder.ConfigureTestServices(services =>
{
services
.ConfigureSilverback()
.UseMockedKafka();
});
MQTT (UseMockedMqtt)
builder.ConfigureTestServices(services =>
{
services
.ConfigureSilverback()
.UseMockedMqtt();
});
Note
UseMockedKafka / UseMockedMqtt replace the underlying broker connectivity (Confluent.Kafka / MQTTnet) with an in-memory implementation.
They don’t add producers/consumers/endpoints for you; those still come from your normal app configuration.
Testing Helpers
The broker-specific testing helper (IKafkaTestingHelper or IMqttTestingHelper) also implements the base ITestingHelper interface.
The most commonly used helpers are:
- WaitUntilConnectedAsync(TimeSpan?) – wait until consumers are connected and ready.
- WaitUntilAllMessagesAreConsumedAsync(params string[]) – wait until routed messages have been processed and committed (mocked brokers only).
- WaitUntilOutboxIsEmptyAsync(TimeSpan?) – wait until the outbox has been drained (if you’re using the outbox).
Example: publish and wait
This pattern is used extensively in our end-to-end tests (see tests/Silverback.Integration.Tests.E2E).
await Host.ConfigureServicesAndRunAsync(
services => services
.AddLogging()
.AddSilverback()
.WithConnectionToMessageBroker(options => options.AddMockedKafka())
.AddKafkaClients(
clients => clients
.WithBootstrapServers("PLAINTEXT://e2e")
.AddProducer(
producer => producer
.Produce<SomeMessage>(endpoint => endpoint.ProduceTo("test-topic")))
.AddConsumer(
consumer => consumer
.WithGroupId("test-group")
.Consume(endpoint => endpoint.ConsumeFrom("test-topic"))))
.AddIntegrationSpyAndSubscriber());
var publisher = Host.ServiceProvider.GetRequiredService<IPublisher>();
await publisher.PublishAsync(new SomeMessage());
await Helper.WaitUntilAllMessagesAreConsumedAsync();
Helper.Spy.InboundEnvelopes.Count.ShouldBe(1);
Important
Don’t assert immediately after producing/publishing. Always wait for the broker pipeline to finish.
With mocked brokers you typically want WaitUntilAllMessagesAreConsumedAsync().
Inspecting Messages with the Integration Spy
When you need to assert what was produced/consumed (including headers, raw payload, endpoint name, etc.), enable the integration spy:
- AddIntegrationSpy(SilverbackBuilder, bool) (default: monitors inbound via a broker behavior)
- AddIntegrationSpyAndSubscriber(SilverbackBuilder) (monitors inbound via a generic subscriber)
services
.AddSilverback()
.WithConnectionToMessageBroker(options => options.AddMockedKafka())
// ...clients/endpoints...
.AddIntegrationSpyAndSubscriber();
// later in the test...
await Helper.WaitUntilAllMessagesAreConsumedAsync();
Helper.Spy.OutboundEnvelopes.ShouldNotBeEmpty();
Helper.Spy.InboundEnvelopes.ShouldNotBeEmpty();
Kafka Specifics
Access in-memory topics
When using the mocked Kafka broker you can inspect the internal topics via GetTopic(string, string?).
var topic = Helper.GetTopic("test-topic");
// For example: inspect partitions/messages (API depends on the topic implementation)
// topic.Partitions...
If your test configures multiple Kafka clusters, use the overload that also takes the bootstrapServers.
Inspect consumer groups
The mocked broker tracks consumer groups and offsets. You can access them via ConsumerGroups or retrieve a specific group via GetConsumerGroup(string).
var group = Helper.GetConsumerGroup("test-group");
// group.Consumers / group.CommittedOffsets ...
Create an ad-hoc producer
Sometimes you want to produce to Kafka without going through the application’s publisher (e.g. to simulate an external producer).
var producer = Helper.GetProducer(
config => config
.WithBootstrapServers("PLAINTEXT://tests")
.ProduceTo("test-topic"));
await producer.ProduceAsync(new SomeMessage());
MQTT Specifics
Inspect client sessions
With the mocked MQTT broker you can inspect client sessions via GetClientSession(string).
var session = Helper.GetClientSession("client-id");
// inspect subscriptions, pending messages, ...
Read published messages
To quickly retrieve the raw MQTT messages published to a topic, use GetMessages(string).
var messages = Helper.GetMessages("test/topic");
messages.Count.ShouldBeGreaterThan(0);
Create an ad-hoc MQTT producer
var producer = Helper.GetProducer(
config => config
.WithClientId("external-producer")
.ProduceTo("test/topic"));
await producer.ProduceAsync(new SomeMessage());