Testing NServiceBus

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

Testing enterprise-scale distributed systems is a challenge. A dedicated NuGet package, NServiceBus.Testing, is provided with tools that allow unit testing endpoint handlers and sagas.

The testing package can be used with any .NET unit testing framework, such as NUnit, xUnit.net or MSTest.

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);

    Assert.AreEqual(1, context.RepliedMessages.Length);
    Assert.IsInstanceOf<MyResponse>(context.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"]);
}

Samples

Related Articles


Last modified