-
-
Notifications
You must be signed in to change notification settings - Fork 364
How to unit test a message handler
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("hello@rebus.fm"));
// assert
A.CallTo(() => invitationService.Invite("hello@rebus.fm")).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("hello@rebus.fm"));
// assert
A.CallTo(() => invitationService.Invite("hello@rebus.fm", thisInstant)).MustHaveHappened();
}
}
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