-
-
Notifications
You must be signed in to change notification settings - Fork 364
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("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"));
}
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.
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