Testing NServiceBus with fluent style

Component: Testing
NuGet Package NServiceBus.Testing (5.x)
Target NServiceBus Version: 5.x
Standard support for version 5.x of NServiceBus has expired. For more information see our Support Policy.
Generally it is recommended to use the Arrange-Act-Assert (AAA) style rather than fluent style. To learn how to test NServiceBus using Arrange-Act-Assert, refer to the sample.
If Xunit test parallization feature is used it is possible that the synchronous variants of OnMessage or WhenXYZ methods will deadlock under certain conditions. The synchronous methods are available for backward compatibility.

Structure

The tests have to follow a well-defined structure otherwise subtle issues may arise at runtime. The recommended structure is presented in the code snippets in this article.

Test.Initialize() (or any of its overloads) must be the first call in all test methods.

Expectations should generally be specified before invoking the tested behaviour (the exception is testing saga timeouts).

If unobtrusive mode is used, the conventions must be configured in the Test.Initialize() method:

Test.Initialize(
    customisations: config =>
    {
        var conventions = config.Conventions();
        conventions.DefiningMessagesAs(MyMessageTypeConvention);
    });

Handlers

Handlers should be tested with a focus on their externally visible behavior: the messages they handle, and those they send, publish, or reply with.

[TestFixture]
public class Tests
{
    [Test]
    public void Run()
    {
        Test.Initialize();

        Test.Handler<MyHandler>()
            .ExpectReply<ResponseMessage>(
                check: message =>
                {
                    return message.String == "hello";
                })
            .OnMessage<RequestMessage>(
                initializeMessage: message =>
                {
                    message.String = "hello";
                });
    }
}

public class MyHandler :
    IHandleMessages<RequestMessage>
{
    public IBus Bus { get; set; }

    public void Handle(RequestMessage message)
    {
        var reply = new ResponseMessage
        {
            String = message.String
        };
        Bus.Reply(reply);
    }
}

This test verifies that, when a message of type RequestMessage is processed by MyHandler, it responds with a message of type ResponseMessage. The test also checks that, if the incoming message has a String property of "hello", then the outgoing message also has a String property of "hello".

Sagas

Sagas should also be tested with a focus on their externally visible behavior: the types of messages they handle, either immediately or after a timeout has expired, and those they send, publish, or reply with.

[Test]
public void Run()
{
    Test.Initialize();
    Test.Saga<MySaga>()
        .ExpectReplyToOriginator<MyResponse>()
        .ExpectTimeoutToBeSetIn<StartsSaga>(
            check: (state, span) =>
            {
                return span == TimeSpan.FromDays(7);
            })
        .ExpectPublish<MyEvent>()
        .ExpectSend<MyCommand>()
        .When(
            sagaIsInvoked: saga =>
            {
                saga.Handle(new StartsSaga());
            })
        .WhenSagaTimesOut()
        .ExpectPublish<MyOtherEvent>()
        .AssertSagaCompletionIs(true);
}

This test verifies that, when a message of type StartsSaga is processed by MySaga, the saga replies to the sender with a message of type MyResponse, publishes a message of type MyEvent, sends a message of type MyCommand, and requests a timeout for message of type StartsSaga. The test also checks if the saga publishes a message of type MyOtherEvent, and that the saga is completed after the timeout expires.

Note that the expectation for the message of type MyOtherEvent is set only after the message is sent.

Interface messages

To support testing of interface messages, use the .WhenHandling<T>() or WhenHandlingAsync<T>() (7.2 and above) method, where T is the interface type.

Header manipulation

Message headers are used to communicate metadata about messages. For example, NServiceBus uses headers to store correlation ID's which are used to associate messages with each other. Headers can be also used for communicate custom information.

[TestFixture]
public class Tests
{
    [Test]
    public void Run()
    {
        Test.Initialize();

        Test.Handler<MyMessageHandler>()
            .SetIncomingHeader("MyHeaderKey", "myHeaderValue")
            .ExpectReply<ResponseMessage>(
                check: message =>
                {
                    return Test.Bus.GetMessageHeader(message, "MyHeaderKey") == "myHeaderValue";
                })
            .OnMessage<RequestMessage>(
                initializeMessage: message =>
                {
                    message.String = "hello";
                });
    }
}

class MyMessageHandler :
    IHandleMessages<RequestMessage>
{
    public IBus Bus { get; set; }

    public void Handle(RequestMessage message)
    {
        var header = Bus.GetMessageHeader(message, "MyHeaderKey");

        var responseMessage = new ResponseMessage();
        Bus.SetMessageHeader(responseMessage, "MyHeaderKey", header);
        Bus.Reply(responseMessage);
    }
}

This test asserts that the outgoing message contains header named "MyHeaderKey" set to "myHeaderValue".

Injecting additional dependencies

Some handlers require other objects to perform their work. When testing those handlers, replace the objects with "stubs" so that the class under test is isolated.

[TestFixture]
public class Tests
{
    [Test]
    public void RunWithDependencyInjection()
    {
        Test.Initialize();

        var mockService = new MyService();
        Test.Handler(
            handlerCreationCallback: bus =>
            {
                return new WithDependencyInjectionHandler(mockService);
            });
        // Rest of test
    }
}

class WithDependencyInjectionHandler :
    IHandleMessages<MyMessage>
{
    MyService myService;

    public WithDependencyInjectionHandler(MyService myService)
    {
        this.myService = myService;
    }

    public void Handle(MyMessage message)
    {
    }
}

Injecting the IBus

In order to access the IBus interface from the tested handler, use constructor injection.

[TestFixture]
public class Tests2
{
    [Test]
    public void RunWithConstructorInjectedBus()
    {
        Test.Initialize();

        var mockService = new MyService();
        Test.Handler(
            handlerCreationCallback: bus =>
            {
                return new WithConstructorInjectedBusHandler(bus);
            });
        // Rest of test
    }
}

class WithConstructorInjectedBusHandler :
    IHandleMessages<MyMessage>
{
    IBus bus;

    public WithConstructorInjectedBusHandler(IBus bus)
    {
        this.bus = bus;
    }

    public void Handle(MyMessage message)
    {
    }
}

Limiting scanning

To limit the assemblies and types scanned, it is possible to use the Initialize() overload that accepts a delegate to customize the ConfigurationBuilder. The list of assemblies scanned must include NServiceBus.Testing.dll

var assembliesToScan = new List<Assembly>
{
    typeof(HandlerToTest).Assembly,
    Assembly.LoadFrom("NServiceBus.Testing.dll")
};
Test.Initialize(
    customisations: configuration =>
    {
        configuration.AssembliesToScan(assembliesToScan);
    });

Samples


Last modified