Table of Contents

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:

Two Ways to Use the Mocked Broker

There are two supported approaches and they solve different problems:

  1. Test-only configuration: your test registers the broker using AddMockedKafka / AddMockedMqtt. This is the simplest approach when your test builds the ServiceCollection (or you fully control the test host setup).

  2. Override an existing configuration: your application configures a real broker in Startup / Program, and your test host overrides that by calling UseMockedKafka / UseMockedMqtt in ConfigureTestServices (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.

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:

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:

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 a Producer on-the-fly

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 a Producer on-the-fly

var producer = Helper.GetProducer(
    config => config
        .WithClientId("external-producer")
        .ProduceTo("test/topic"));

await producer.ProduceAsync(new SomeMessage());

Envelope Builders

Builders are also available to create the IInboundEnvelope<TMessage> and IOutboundEnvelope<TMessage> for the unit tests:

var envelope = new InboundEnvelopeBuilder<MyMessage>()
    .WithMessage(new MyMessage() { ... })
    .WithKafkaKey("1234")
    .WithKafkaTopic("test-topic")
    .Build();