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.
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.That(repliedMessages.Length, Is.EqualTo(1));
Assert.That(repliedMessages[0].Message, Is.InstanceOf<MyResponse>());
}
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.That(processMessage.TotalAmount, Is.EqualTo(900));
}
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.That(context.Headers["custom-header"], Is.EqualTo("custom header value"));
}
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.That(sentMessages.Length, Is.EqualTo(1));
Assert.That(sentMessages[0].Message, Is.InstanceOf<MyMessage>());
}