Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork371
How to create integration tests
As you can probably imagine, writing integration tests is slightly more involved than writing ordinary unit tests. By nature, they contain more moving parts, so we need to handle some more problems, e.g. regarding timing and stuff.
Rebus is pretty nice though, because it has in-mem implementations of its transport and storage for subscriptions, sagas, and timeouts.
So, basically you can start a full bus running on in-mem persistence all the way through like this:
[Test]publicvoidMyTest(){usingvaractivator=newBuiltinHandlerActivator();usingvarbus=Configure.With(activator).Transport(t=>t.UseInMemoryTransport(newInMemNetwork(),"queue-name")).Subscriptions(s=>s.StoreInMemory()).Sagas(s=>s.StoreInMemory()).Timeouts(t=>t.StoreInMemory()).Start();// exercise bus here}
If you let two bus instances share theInMemNetwork passed toUseInMemoryTransport, they can communicate, so that's how you can run realistic integration tests on your build server without any message broker available, and without the concurrency and state management problems that that would incur.
Similarly, if you pass anInMemorySubscriberStore instance toStoreInMemory under theSubscriptions configurer, you can integration test a simpleSystem.String pub/sub scenario like this:
[Test]publicasyncTaskSubscriberGetsPublishedStrings(){varnetwork=newInMemNetwork();varsubscriberStore=newInMemorySubscriberStore();usingvarpublisherActivator=newBuiltinHandlerActivator();usingvarsubscriberActivator=newBuiltinHandlerActivator();usingvareventWasReceived=newManualResetEvent(initialState:false);usingvarpublisher=Configure.With(publisherActivator)// rebus v7+: Configure.OneWayClient().Transport(t=>t.UseInMemoryTransport(network,"publisher")).Subscriptions(s=>s.StoreInMemory(subscriberStore)).Start();subscriberActivator.Handle<string>(async message=>eventWasReceived.Set());varsubscriber=Configure.With(subscriberActivator).Transport(t=>t.UseInMemoryTransport(network,"subscriber")).Subscriptions(s=>s.StoreInMemory(subscriberStore)).Start();awaitsubscriber.Subscribe<string>();awaitpublisher.Publish("HEJ MED DIG MIN VEN");Assert.That(eventWasReceived.WaitOne(TimeSpan.FromSeconds(5)),Is.True,"Did not receive the published event within 5 s timeout");}
in this case using aManualResetEvent to block and wait for the subscriber to receive the published string, failing with anAssertionException if it's not received within a 5 s timeout.
Another great option is to use theHypothesist asynchronous assertion framework with a providedadapter for Rebus:
[Test]publicasyncTaskSubscriberGetsPublishedStrings(){// Arrangevarhypothesis=Hypothesis.For<string>().Any(x=>x=="HEJ MED DIG MIN VEN");// hypothesist for async assertionsusingvaractivator=newBuiltinHandlerActivator().Register(hypothesis.AsHandler);// adapater to register the hypothesis as handler// remaining setup equals previous example, except for the manual reset event.awaithypothesis.Validate(TimeSpan.FromSeconds(5));}
While this might seem fairly straightforward, here's a little word of warning: Writing integration tests like this can quickly become complicated, so my advice is to exercise the same amount of care and discipline as you would with your production code. But that actually holds for all of your testing code – the same patterns and anti-patterns apply there too, because why wouldn't they? 😉
One of the important distinctions to make early on in my experience, is between code that would want to share between your system and your tests, and code you want to be able to swap out with test code, like the example above.
I often end up with using configuration code that looks like this:
Configure.With(activator).Transport(t=>{if(Backdoor.Network!=null){t.UseInMemoryTransport(Backdoor.Network,"queue-name");}else{t.UseMsmq("queue-name");}}).Subscriptions(s=>{if(Backdoor.SubscriberStore!=null){s.StoreInMemory(Backdoor.SubscriberStore);}else{s.StoreInSqlServer(connectionString,"Subscriptions",isCentralized:true);}}).Start();
and then I have aBackdoor lying around in the project that looks somewhat like this:
internalstaticclassBackdoor{internalstaticInMemNetworkNetwork;internalstaticInMemorySubscriberStoreSubscriberStore;publicstaticvoidReset(){Network=null;SubscriberStore=null;}publicstaticvoidEnableTestMode(){Network=newInMemNetwork();SubscriberStore=newInMemorySubscriberStore();}}
Combined with[InternalsVisibleTo("MyTestProject")] I can then
Backdoor.EnableTestMode();
and
Backdoor.Reset();
before and after each test in myBusIntegrationTestFixtureBase.
This way, the configuration code I use in my integration tests is the same as the configuration code I use in my production system, save for the few lines that configure which queue and subscription storage I'm using.
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