Skip to content

How to unit test a message handler

Mogens Heller Grabe edited this page Jun 21, 2019 · 4 revisions

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 newing 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("hello@rebus.fm"));

        // assert
        A.CallTo(() => invitationService.Invite("hello@rebus.fm")).MustHaveHappened();
    }
}

and that is basically it! 😄

Injecting a message context

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("hello@rebus.fm"));

        // assert
        A.CallTo(() => invitationService.Invite("hello@rebus.fm", thisInstant)).MustHaveHappened();
    }
}
Clone this wiki locally