Skip to content

How to test code that uses the bus to do things

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

When your code depends on IBus and uses it to send messages, you can sometimes get away with using dynamically created mocks (e.g. by using something like FakeItEasy).

Other times, you have more stuff going on, which would be too tedious to test using generic mocking libraries. Therefore, Rebus' test helpers come with FakeBus, which is an implementation of IBus that simply records all the stuff you do to it.

Getting started

To get started with it, install the Rebus.TestHelpers package via NuGet, and then you simply do something like this:

// create the fake bus
var bus = new FakeBus();

// exercise your logic
new SomeKindOfSut(bus).DoStuff();

// assert that your SUT did what it should have done
var events = bus.Events; // IEnumerable of things that happened

Full example

An example could be an implementation of the IInvitationService that was mentioned on the How to unit test a message handler page:

public interface IInvitationService
{
    Task Invite(string emailAddress, DateTimeOffset invitationTime);
    Task ResendInvite(string emailAddress);
}

We now pretend that our implementation of IInvitationService is DefaultInvitationService, and we want to test its behavior when we call Invite on it. It uses the bus to send an email to the provided email address, so that's what we're going to check.

The logic looks like this:

class DefaultInvitationService : IInvitationService
{
    readonly IEmailTemplateService _emailTemplateService;
    readonly IBus _bus;

    public DefaultInvitationService(IBus bus, IEmailTemplateService emailTemplateService)
    {
        _bus = bus;
        _emailTemplateService = emailTemplateService;
    }

    public async Task Invite(string emailAddress, DateTimeOffset invitationTime)
    {
        var (subject, body) = _emailTemplateService
            .GetInvitationTemplate(
                emailAddress: emailAddress,
                invitationTime: invitationTime
            );

        await _bus.Send(new SendEmail(
            to: emailAddress,
            subject: subject,
            body: body
        ));
    }

    public async Task ResendInvite(string emailAddress)
    {
    	// ignore for now
    }
}

As you can see, the Invite method asks some kind of email template service for an appropriate email subject and body to use, so our test just needs to set up some known values to return from that and then verify that the SendEmail command got sent as expected.

Using FakeItEasy to mock the IEmailTemplateService stuff, we can write a test like this:

[Test]
public async Task SendsEmailAsExpected()
{
    // arrange
    var fakeBus = new FakeBus();
    var emailTemplateService = A.Fake<IEmailTemplateService>();
    var sut = new DefaultInvitationService(fakeBus, emailTemplateService);

    var now = DateTimeOffset.Now;
    
    A.CallTo(() => emailTemplateService.GetInvitationTemplate("hello@rebus.fm", now))
        .Returns((subject: "interesting subject", body: "great body"));

    // act
    await sut.Invite("hello@rebus.fm", now);

    // assert
    var sentEmailCommand = fakeBus.Events
        .OfType<MessageSent<SendEmail>>()
        .Single()
        .CommandMessage;

    Assert.That(sentEmailCommand.To, Is.EqualTo("hello@rebus.fm"));
    Assert.That(sentEmailCommand.Subject, Is.EqualTo("interesting subject"));
    Assert.That(sentEmailCommand.Body, Is.EqualTo("great body"));
}

Types of events recorded by fake bus

The Events property of FakeBus returns an IEnumerable<FakeBusEvent>, where FakeBusEvent is an abstract class, which is root of the following inheritance hierarchy:

  • FakeBusEvent
    • MessageDeferred
      • MessageDeferred<TMessage>
    • MessageDeferredToDestination
      • MessageDeferredToDestination<TMessage>
    • MessageDeferredToSelf
      • MessageDeferredToSelf<TMessage>
    • MessagePublished
      • MessagePublished<TMessage>
    • MessagePublishedToTopic
      • MessagePublishedToTopic<TMessage>
    • MessageSent
      • MessageSent<TMessage>
    • MessageSentToDestination
      • MessageSentToDestination<TMessage>
    • MessageSentToSelf
      • MessageSentToSelf<TMessage>
    • MessageSentWithRoutingSlip
      • MessageSentWithRoutingSlip<TMessage>
    • NumberOfWorkersChanged
    • ReplyMessageSent
      • ReplyMessageSent<TMessage>
    • Subscribed
    • SubscribedToTopic
    • TransportMessageDeferred
    • TransportMessageForwarded
    • Unsubscribed
    • UnsubscribedFromTopic
    • FakeBusDisposed

The event names should be pretty self-explanatory... so, as you can see, it's pretty easy to verify that your code uses IBus as expected.

What about ISyncBus?

Btw. if your code relies on ISyncBus to do its thing, there's a functionally similar FakeSyncBus, that you can use. 😊

Clone this wiki locally