NServiceBus Testing Upgrade Version 7 to 8

Component: Testing
This page targets a pre-release version. Pre-releases are subject to change and samples are not guaranteed to be fully functional.

Fluent-style API is not supported

The fluent-style testing API is no longer supported. Tests should be written in an Arrange-Act-Assert (AAA) style. Tests written this way will simply create the handler or saga to be tested, and call methods on them directly, passing in a testable message handler context that will capture outgoing operations that can be asserted on afterwards.

Testing a handler

To test a handler, create it with any necessary dependencies, then call the Handle method directly. Pass in an instance of TestableMessageHandlerContext which will collect all outgoing operations. This context allows customization of metadata about the incoming message, including headers.

An example of the same test written in Arrange-Act-Assert style and Fluent style:

8-pre NServiceBus.Testing
[TestFixture]
class ArrangeActAssertHandlerTesting
{
    [Test]
    public async Task TestHandler()
    {
        // Arrange
        var handler = new RequestMessageHandler();
        
        // Act
        var requestMessage = new RequestMessage {String = "hello"};
        var messageContext = new TestableMessageHandlerContext();
        await handler.Handle(requestMessage, messageContext);

        // Assert
        Assert.IsTrue(messageContext.RepliedMessages.Any(x =>
            x.Message<ResponseMessage>()?.String == "hello"),
            "Should send a ResponseMessage reply that echoes the provided string");
    }
}
7.x NServiceBus.Testing
[TestFixture]
class FluentHandlerTesting
{
    [Test]
    public async Task TestHandler()
    {
        await Test.Handler<RequestMessageHandler>() // Arrange
            .ExpectReply<ResponseMessage>( // Assert
                check: message =>
                {
                    return message.String == "hello";
                })
            .OnMessageAsync<RequestMessage>( // Act 
                initializeMessage: message =>
                {
                    message.String = "hello";
                });
    }
}

See the handler unit testing documentation for more information.

Testing a saga

To test a saga, create it with any necessary dependencies (including setting the Data property), then call the Handle method directly. Pass in an instance of TestableMessageHandlerContext which will collect all outgoing operations. This context allows customization of metadata about the incoming message, including headers.

Fluent-saga tests frequently will include multiple state changes. Arrange Act Assert (AAA) tests should test a single state change in isolation. The state of the saga can be manually configured before each test as part of the Arrange step.

This is an example showing two state changes. One to start the saga that triggers a timeout, sends a reply, publishes an event, and sends a message. The second state change happens when the timeout occurs, causing another event to be published, and the saga to be completed. These can be split into two tests (one for each state change) when using the Arrange-Act-Assert style.

8-pre NServiceBus.Testing
[TestFixture]
class ArrangeActAssertSagaTests
{
    [Test]
    public async Task When_Saga_is_started_by_StartsSaga()
    {
        // Arrange
        var saga = new MySaga
        {
            Data = new MySaga.SagaData
            {
                Originator = "Originator"
            }
        };

        // Act
        var message = new StartsSaga();
        var messageHandlerContext = new TestableMessageHandlerContext();

        await saga.Handle(message, messageHandlerContext);

        // Assert
        Assert.IsTrue(messageHandlerContext.RepliedMessages.Any(x =>
            x.Message is MyResponse &&
            x.Options.GetDestination() == "Originator"),
            "A MyResponse reply should be sent to the originator"
        );

        Assert.IsTrue(messageHandlerContext.TimeoutMessages.Any(x =>
            x.Message is StartsSaga &&
            x.Within == TimeSpan.FromDays(7)), 
            "The StartsSaga message should be deferred for 7 days"
        );

        Assert.IsTrue(messageHandlerContext.PublishedMessages.Any(x =>
            x.Message is MyEvent), 
            "MyEvent should be published"
        );

        Assert.IsTrue(messageHandlerContext.SentMessages.Any(x =>
            x.Message is MyCommand),
            "MyCommand should be sent"
        );
    }

    [Test]
    public async Task When_StartsSaga_Timeout_completes()
    {
        // Arrange
        var saga = new MySaga
        {
            Data = new MySaga.SagaData()
        };

        // Act
        var timeoutMessage = new StartsSaga();
        var timeoutHandlerContext = new TestableMessageHandlerContext();
        await saga.Timeout(timeoutMessage, timeoutHandlerContext);
        
        // Assert
        Assert.IsTrue(timeoutHandlerContext.PublishedMessages.Any(x =>
            x.Message is MyOtherEvent),
            "MyOtherEvent should be published"
        );

        Assert.IsTrue(saga.Completed, "Saga should be completed");
    }
}
7.x NServiceBus.Testing
[TestFixture]
class FluentSagaTests
{
    [Test]
    public void TestSagaFluent()
    {
        Test.Saga<MySaga>()
            .ExpectReplyToOriginator<MyResponse>()
            .ExpectTimeoutToBeSetIn<StartsSaga>(
                check: (state, span) =>
                {
                    return span == TimeSpan.FromDays(7);
                })
            .ExpectPublish<MyEvent>()
            .ExpectSend<MyCommand>()
            .When(
                sagaIsInvoked: (saga, context) =>
                {
                    return saga.Handle(new StartsSaga(), context);
                })
            .ExpectPublish<MyOtherEvent>()
            .WhenSagaTimesOut()
            .ExpectSagaCompleted();
    }
}

See the saga unit testing documentation for more information.

Related Articles


Last modified