Unit Testing NServiceBus

Component: Testing
NuGet Package NServiceBus.Testing (6.x)
Target NServiceBus Version: 6.x

This sample shows how to write unit tests for various NServiceBus components with Arrange-Act-Assert (AAA) style tests. This sample is a test project that uses NUnit, and utilizes testable helper implementations from the NServiceBus.Testing package.

Testing a handler

Given the following handler:

public class MyReplyingHandler :
    IHandleMessages<MyRequest>
{
    public Task Handle(MyRequest message, IMessageHandlerContext context)
    {
        return context.Reply(new MyResponse());
    }
}

The test that verifies a Reply happened:

[Test]
public async Task ShouldReplyWithResponseMessage()
{
    var handler = new MyReplyingHandler();
    var context = new TestableMessageHandlerContext();

    await handler.Handle(new MyRequest(), context)
        .ConfigureAwait(false);

    var repliedMessages = context.RepliedMessages;
    Assert.AreEqual(1, repliedMessages.Length);
    Assert.IsInstanceOf<MyResponse>(repliedMessages[0].Message);
}

Testing a Saga

Here's an example of a Saga, that processes an order, and gives a 10% discount for orders above the amount of 1000:

public class DiscountPolicy :
    Saga<DiscountPolicyData>,
    IAmStartedByMessages<SubmitOrder>
{
    public Task Handle(SubmitOrder message, IMessageHandlerContext context)
    {
        Data.CustomerId = message.CustomerId;
        Data.TotalAmount += message.TotalAmount;

        if (Data.TotalAmount >= 1000)
        {
            return ProcessWithDiscount(message, context);
        }
        return ProcessOrder(message, context);
    }

    Task ProcessWithDiscount(SubmitOrder message, IMessageHandlerContext context)
    {
        var processOrder = new ProcessOrder
        {
            CustomerId = Data.CustomerId,
            OrderId = message.OrderId,
            TotalAmount = message.TotalAmount * (decimal)0.9
        };
        return context.Send(processOrder);
    }

    Task ProcessOrder(SubmitOrder message, IMessageHandlerContext context)
    {
        var processOrder = new ProcessOrder
        {
            CustomerId = Data.CustomerId,
            OrderId = message.OrderId,
            TotalAmount = message.TotalAmount
        };
        return context.Send(processOrder);
    }

    protected override void ConfigureHowToFindSaga(SagaPropertyMapper<DiscountPolicyData> mapper)
    {
    }
}

The following unit tests checks that the total amount has the discount applied:

[Test]
public async Task ShouldProcessDiscountOrder()
{
    var saga = new DiscountPolicy
    {
        Data = new DiscountPolicyData()
    };
    var context = new TestableMessageHandlerContext();

    var discountOrder = new SubmitOrder
    {
        CustomerId = Guid.NewGuid(),
        OrderId = Guid.NewGuid(),
        TotalAmount = 1000
    };

    await saga.Handle(discountOrder, context)
        .ConfigureAwait(false);

    var processMessage = (ProcessOrder)context.SentMessages[0].Message;
    Assert.AreEqual(900, processMessage.TotalAmount);
}

Testing a behavior

The following custom behavior adds a header to an outgoing message in case the message is of the type MyResponse:

public class CustomBehavior :
    Behavior<IOutgoingLogicalMessageContext>
{
    public override Task Invoke(IOutgoingLogicalMessageContext context, Func<Task> next)
    {
        if (context.Message.MessageType == typeof(MyResponse))
        {
            context.Headers.Add("custom-header", "custom header value");
        }

        return next();
    }
}

The behavior can be tested similar to a message handler or a Saga by using a testable representation of the context:

[Test]
public async Task ShouldAddCustomHeaderToMyResponse()
{
    var behavior = new CustomBehavior();
    var context = new TestableOutgoingLogicalMessageContext
    {
        Message = new OutgoingLogicalMessage(typeof(MyResponse), new MyResponse())
    };

    await behavior.Invoke(context, () => Task.CompletedTask)
        .ConfigureAwait(false);

    Assert.AreEqual("custom header value", context.Headers["custom-header"]);
}

Testing IEndpointInstance usage

IEndpointInstance is the main entry point for messaging APIs when used outside the pipeline (i.e. not Sagas or Handlers). One such common example is sending a message from a webpage controller (or any other web-request handling code). For example a controller, with an injected IEndpointInstance, handles a request and sends a message.

public class MyController
{
    IEndpointInstance endpointInstance;

    public MyController(IEndpointInstance endpointInstance)
    {
        this.endpointInstance = endpointInstance;
    }

    public Task HandleRequest()
    {
        return endpointInstance.Send(new MyMessage());
    }
}

The test that verifies a Send happened:

[Test]
public async Task ShouldSendMessage()
{
    var endpointInstance = new TestableEndpointInstance();
    var handler = new MyController(endpointInstance);

    await handler.HandleRequest()
        .ConfigureAwait(false);

    var sentMessages = endpointInstance.SentMessages;
    Assert.AreEqual(1, sentMessages.Length);
    Assert.IsInstanceOf<MyMessage>(sentMessages[0].Message);
}

Related Articles

  • Testing NServiceBus
    Develop service layers and long-running processes using test-driven development.

Last modified