Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring

Unit Testing NServiceBus

Component: Testing
NuGet Package: NServiceBus.Testing (7.x)
Target Version: NServiceBus 7.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 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 following test verifies that the handler received a Reply:

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

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

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

Testing a saga

See saga scenario testing to see an alternate method of saga testing where an entire scenario consisting of multiple messages can be tested at once.

Here's an example of a saga that processes an order and gives a 10% discount for orders above 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)
    {
        mapper.MapSaga(saga => saga.CustomerId)
            .ToMessage<SubmitOrder>(msg => msg.CustomerId);
    }
}

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

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

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

    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. outside a saga or handler). One common example is sending a message from a webpage controller. For example the following controller has an injected IEndpointInstance and 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();

    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.