Testing NServiceBus with fluent style

Component: Testing
NuGet Package: NServiceBus.Testing (7.x)
Target Version: NServiceBus 7.x
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 the xUnit test parallelization 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

Tests may be written using the fluent API or the traditional Arrange-Act-Assert (AAA) approach.

This article describes the fluent API. To learn how to test NServiceBus using the Arrange-Act-Assert approach, refer to the sample.

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 async Task TestHandler()
    {
        await Test.Handler<MyHandler>()
            .ExpectReply<ResponseMessage>(
                check: message =>
                {
                    return message.String == "hello";
                })
            .OnMessageAsync<RequestMessage>(
                initializeMessage: message =>
                {
                    message.String = "hello";
                });
    }
}

public class MyHandler :
    IHandleMessages<RequestMessage>
{
    public Task Handle(RequestMessage message, IMessageHandlerContext context)
    {
        var reply = new ResponseMessage
        {
            String = message.String
        };
        return context.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 async Task TestSagaAsync()
{
    var sagaExpectations = await Test.Saga<MySaga>()
        .ExpectReplyToOriginator<MyResponse>()
        .ExpectTimeoutToBeSetIn<StartsSaga>(
            check: (state, span) =>
            {
                return span == TimeSpan.FromDays(7);
            })
        .ExpectPublish<MyEvent>()
        .ExpectSend<MyCommand>()
        .WhenAsync(
            sagaIsInvoked: (saga, context) =>
            {
                return saga.Handle(new StartsSaga(), context);
            });

    await sagaExpectations
        .ExpectPublish<MyOtherEvent>()
        .ExpectSagaCompleted()
        .WhenSagaTimesOutAsync();
}

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 IDs which are used to associate messages with each other. Headers can be also used to communicate custom information.

[TestFixture]
public class Tests
{
    [Test]
    public async Task Run()
    {
        await Test.Handler<MyMessageHandler>()
            .SetIncomingHeader("MyHeaderKey", "myHeaderValue")
            .ExpectReply<ResponseMessage>(
                check: (message, replyOptions) =>
                {
                    var headers = replyOptions.GetHeaders();
                    return headers["MyHeaderKey"] == "myHeaderValue";
                })
            .OnMessageAsync<RequestMessage>(
                initializeMessage: message =>
                {
                    message.String = "hello";
                });
    }
}

class MyMessageHandler :
    IHandleMessages<RequestMessage>
{
    public Task Handle(RequestMessage message, IMessageHandlerContext context)
    {
        var header = context.MessageHeaders["MyHeaderKey"];

        var responseMessage = new ResponseMessage();

        var options = new ReplyOptions();
        options.SetHeader("MyHeaderKey", header);
        return context.Reply(responseMessage, options);
    }
}

This test asserts that the outgoing message contains a 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()
    {
        var mockService = new MyService();
        var injectionHandler = new WithDependencyInjectionHandler(mockService);
        var handler = Test.Handler(injectionHandler);
        // Rest of test
    }
}

class WithDependencyInjectionHandler :
    IHandleMessages<MyMessage>
{
    MyService myService;

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

    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        // use myService here
        return Task.CompletedTask;
    }
}

Samples


Last modified