-
-
Couldn't load subscription status.
- Fork 370
How to test code that uses the bus to do things
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.
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
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("[email protected]", now))
.Returns((subject: "interesting subject", body: "great body"));
// act
await sut.Invite("[email protected]", now);
// assert
var sentEmailCommand = fakeBus.Events
.OfType<MessageSent<SendEmail>>()
.Single()
.CommandMessage;
Assert.That(sentEmailCommand.To, Is.EqualTo("[email protected]"));
Assert.That(sentEmailCommand.Subject, Is.EqualTo("interesting subject"));
Assert.That(sentEmailCommand.Body, Is.EqualTo("great body"));
}The Events property of FakeBus returns an IEnumerable<FakeBusEvent>, where FakeBusEvent is an abstract class, which is root of the following inheritance hierarchy:
-
FakeBusEvent-
MessageDeferredMessageDeferred<TMessage>
-
MessageDeferredToDestinationMessageDeferredToDestination<TMessage>
-
MessageDeferredToSelfMessageDeferredToSelf<TMessage>
-
MessagePublishedMessagePublished<TMessage>
-
MessagePublishedToTopicMessagePublishedToTopic<TMessage>
-
MessageSentMessageSent<TMessage>
-
MessageSentToDestinationMessageSentToDestination<TMessage>
-
MessageSentToSelfMessageSentToSelf<TMessage>
-
MessageSentWithRoutingSlipMessageSentWithRoutingSlip<TMessage>
NumberOfWorkersChanged-
ReplyMessageSentReplyMessageSent<TMessage>
SubscribedSubscribedToTopicTransportMessageDeferredTransportMessageForwardedUnsubscribedUnsubscribedFromTopicFakeBusDisposed
-
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.
Btw. if your code relies on ISyncBus to do its thing, there's a functionally similar FakeSyncBus, that you can use. 😊
Basic stuff
- Home
- Introduction
- Getting started
- Different bus modes
- How does rebus compare to other .net service buses?
- 3rd party extensions
- Rebus versions
Configuration
Scenarios
Areas
- Logging
- Routing
- Serialization
- Pub sub messaging
- Process managers
- Message context
- Data bus
- Correlation ids
- Container adapters
- Automatic retries and error handling
- Message dispatch
- Thread safety and instance policies
- Timeouts
- Timeout manager
- Transactions
- Delivery guarantees
- Idempotence
- Unit of work
- Workers and parallelism
- Wire level format of messages
- Handler pipeline
- Polymorphic message dispatch
- Persistence ignorance
- Saga parallelism
- Transport message forwarding
- Testing
- Outbox
- Startup/shutdown
Transports (not a full list)
Customization
- Extensibility
- Auto flowing user context extensibility example
- Back off strategy
- Message compression and encryption
- Fail fast on certain exception types
Pipelines
- Log message pipelines
- Incoming messages pipeline
- Incoming step context
- Outgoing messages pipeline
- Outgoing step context
Prominent application services