-
-
Notifications
You must be signed in to change notification settings - Fork 364
Unit testing
When you are developing software, you probably want to ensure that you can bend and flex your code and refactor it to your heart's desire without breaking anything.
You probably do that by writing automated tests that test units of your system at various levels.
This page will describe three different levels of testing with Rebus.
Since Rebus can be configured with an in-memory transport and in-memory persistence, you can get away with testing many things by starting a full Rebus instance to participate in your tests without risking tests interfering with each other, or risking that the environment somehow affects the outcome of your tests.
This approach can be very useful to verify that messages flow like you expect them to, since you can easily spin up multiple endpoints that communicate using the in-mem transport - you just need to pass the same Network
instance to them so that they can reach each other - e.g. like so:
[Test]
public void DoSomeTesting()
{
var network = new InMemNetwork();
var containerAdapter1 = GetContainerAdapter1();
var containerAdapter2 = GetContainerAdapter2();
using (var bus1 = CreateBus("b1", network, containerAdapter1))
using (var bus2 = CreateBus("b2", network, containerAdapter2))
{
// do stuff in here
}
}
IBus CreateBus(string inputQueueName, InMemNetwork network, IContaineAdapter containerAdapter)
{
return Configure.With(containerAdapter)
.Transport(t => t.UseInMemoryTransport(network, inputQueueName))
.Start();
}
Sometimes you might want to be sure that one particular handler makes certain calls when a message is dispatched to it. Since message handlers just need to implement IHandleMessages<TMessage>
your handler is just an ordinary class with a Handle
method on it, like so:
public class MyMessageHandler : IHandleMessage<MyMessage>
{
public async Task Handle(MyMessage message)
{
// whee!
}
}
You can - obviously - punish MyMessageHandler
as hard as you wish in your tests, and you will not be bothered by Rebus in any way. But what if we want to verify that MyMessageHandler
sends an AnotherMessage
when it receives a MyMessage
with a certain name in it?
Consider this slightly modified version of MyMessageHandler
:
public class MyMessageHandler : IHandleMessage<MyMessage>
{
readonly IBus _bus;
public MyMessageHandler(IBus bus)
{
_bus = bus;
}
public async Task Handle(MyMessage message)
{
if (message.Name == "El Duderino")
{
await _bus.Send(new AnotherMessage());
}
}
}
Since mocking IBus
can be pretty tedious (mainly because all methods are async and return Task
s), you can now benefit from using Rebus' FakeBus
to participate in this kind of test.
In this case, the test would look like this:
[Test]
public void CheckThatWeReactProperlyToTheDude()
{
var bus = new FakeBus();
var handler = new MyMessageHandler(bus);
handler.Handle(new MyMessage { Name="El Duderino" }).Wait();
var messageSentEvents = bus.Events.OfType<MessageSent<AnotherMessage>>();
Assert.That(messageSentEvents.Count(), Is.EqualTo(1));
}
As you can see, FakeBus
records everything that happens to it as events. And these events can be queried with your ordinary LINQ via the Events
property, which returns an IEnumerable<FakeBusEvent>
.
FakeBusEvent
derivations exist for MessageSent
, MessageDeferred
, MessagePublished
, etc.
Unit testing a saga handler is a little more involved. Saga handler are derived from Rebus' Saga<TSagaData>
class, so you inherit a little bit of behavior with it.
Moreover, a saga needs to set up correlation properties, i.e. how to figure out which saga instance to load for each incoming message.
Even though it would be possible to just new
up your saga handler and dispatch a message to it by calling a Handle
method on it, this kind of testing would skip a crucial part of the logic associated with a saga: Message correlation!
Therefore - to make it easier to test sagas, including whichever correlation you have set up - Rebus comes with a SagaFixture<TSagaHandler>
.
Say, for instance, that you want to test a saga called MySaga
- that could be done like this:
using(var fixture = SagaFixture.For<MySaga>())
{
// perform test in here
}
or - if the saga handler does not come with a default constructor:
using(var fixture = SagaFixture.For(() => new MySaga(someDependency)))
{
// perform test in here
}
The saga fixture has all kinds of neat things on it. You can deliver messages to it by calling its Deliver
method, like so:
fixture.Deliver("are you there?");
and you can check existing saga data instances by doing some LINQ on the saga fixture's Data
property, e.g. like so:
var myInstance = fixture.Data
.OfType<MySagaData>()
.First(d => d.CorrelationId == "blah");
Internally, saga fixture uses a full in-memory Rebus bus - it just blocks until the message has been fully processed when you Deliver
a message to it, and then its in-mem saga storage exposes all contained saga data instances to be queries using the syntax shown above.
Happy testing! 😁
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