How to unit test a message handler - rebus-org/Rebus GitHub Wiki
This is easy: Since Rebus handlers are just your own classes that implement IHandleMessages<TMessage>
, and since your messages are most likely simple DTOs, then testing your handlers is as simple as new
ing them up, possibly injecting some fake things, and then exercising them like you would any other class.
Consider this example, where my handler for the simple InviteNewUserByEmail
message looks like this:
public class InviteNewUserByEmailHandler : IHandleMessages<InviteNewUserByEmail>
{
readonly IInvitationService _invitationService;
public InviteNewUserByEmailHandler(IInvitationService invitationService)
{
_invitationService = invitationService;
}
public async Task Handle(InviteNewUserByEmail message)
{
await _invitationService.Invite(message.EmailAddress);
}
}
If I want to test this and verify, that the Invite
method of IInvitationService
is called as expected, I can simply do this (here using FakeItEasy
to create a dynamic mock):
[TestFixture]
public class InviteNewUserByEmailHandlerTest
{
[Test]
public async Task CanInviteNewUserByEmail()
{
// arrange
var invitationService = A.Fake<IInvitationService>();
var handler = new InviteNewUserByEmailHandler(invitationService);
// act
await handler.Handle(new InviteNewUserByEmail("[email protected]"));
// assert
A.CallTo(() => invitationService.Invite("[email protected]")).MustHaveHappened();
}
}
and that is basically it! 😄
In some situations, your code might need some stuff that Rebus' message context can provide. All the supported IoC container adapters automatically set up a resolver for IMessageContext
, so your handler can have the context injected into its constructor.
So let's pretend that our IInvitationService
needs the time of when the command was sent, and we want to grab the timestamp automatically provided by Rebus as the rbs2-senttime
header – the updated handler code looks like this:
public class InviteNewUserByEmailHandler : IHandleMessages<InviteNewUserByEmail>
{
readonly IInvitationService _invitationService;
readonly IMessageContext _messageContext;
public InviteNewUserByEmailHandler(IInvitationService invitationService, IMessageContext messageContext)
{
_invitationService = invitationService;
_messageContext = messageContext;
}
public async Task Handle(InviteNewUserByEmail message)
{
var headerValue = _messageContext.Headers.GetValue(Headers.SentTime);
var sentTime = DateTimeOffset.ParseExact(headerValue, "o", null, DateTimeStyles.RoundtripKind);
await _invitationService.Invite(message.EmailAddress, sentTime);
}
}
To be able to test this, we could create a dynamic mock for IMessageContext
and inject that, similar to how we mock the IInvitationService
. That would be fairly easy, and it would definitely be a valid way to test this. But Rebus also comes with a FakeMessageContext
that you can use – just install the Rebus.TestHelpers
NuGet package, and then you can do this:
[TestFixture]
public class InviteNewUserByEmailHandlerTest
{
[Test]
public async Task CanInviteNewUserByEmail()
{
// arrange
var thisInstant = new DateTimeOffset(2019, 6, 20, 16, 17, 30, TimeSpan.FromHours(2));
var headers = new Dictionary<string, string>
{
{Headers.SentTime, thisInstant.ToString("o")}
};
var fakeMessageContext = new FakeMessageContext(headers: headers);
var invitationService = A.Fake<IInvitationService>();
var handler = new InviteNewUserByEmailHandler(invitationService, fakeMessageContext);
// act
await handler.Handle(new InviteNewUserByEmail("[email protected]"));
// assert
A.CallTo(() => invitationService.Invite("[email protected]", thisInstant)).MustHaveHappened();
}
}