Developing enterprise-scale distributed systems is hard
and testing them is just as challenging a task.
The architectural approach supported by NServiceBus
makes these challenges more manageable. And the
testing facilities provided actually make
unit testing endpoints and workflows easy.
Developers can now develop their service layers
and long-running processes using test-driven development.
Unit testing the service layer
The service layer in an NServiceBus application is made out of message handlers. Each class typically handles one specific type of message. Testing these classes is most often focused on their externally visible behavior - the types of messages they send or reply with. This is as simple to test as could be expected:
[Test] public void TestHandler() { Test.Initialize(); Test.Handler<YourMessageHandler>() .ExpectReply<YourResponseMessage>(m => m.String == "hello") .OnMessage<YourRequestMessage>(m => m.String = "hello"); } [Test] public void TestSaga() { Test.Saga<YourSaga>() .ExpectReplyToOrginator<ResponseToOriginator>() .ExpectTimeoutToBeSetIn<StartsSaga>((state, span) => span == TimeSpan.FromDays(7)) .ExpectPublish<Event>() .ExpectSend<Command>() .When(s => s.Handle(new StartsSaga())) .ExpectPublish<Event>() .WhenSagaTimesOut() .AssertSagaCompletionIs(true); }
What this test is saying that when a message of the type YourRequestMessage is processed by YourMessageHandler, it will respond with a message of the type YourResponseMessage. Also, if the request message's String property value is "hello" than that will also be the value of the String property of the response message.
Testing header manipulation
One of the responsibilities of the message handlers in the service layer is using data from headers found in the request to make decisions, as well as setting headers on top of the response messages. This is how this kind of functionality can be tested:
[Test] public void TestHandler() { Test.Initialize(); Test.Handler<YourMessageHandler>() .SetIncomingHeader("Test", "abc") .ExpectReply<YourResponseMessage>(m => m.String == "hello") .AssertOutgoingHeader("Test", "abc") .OnMessage<YourRequestMessage>(m => m.String = "hello"); }
This test sets the value of the incoming header key "Test" to be the value "abc". The test also asserts that the value of the outgoing header key "Test" will be "abc" as well.
Injecting additional dependencies into the service layer
Many of the message handling classes in the service layer will make use of other objects to perform their work. When testing these classes, you'll want to replace those objects with "stubs" so that the class under test is isolated. Here's how that's done:
[Test] public void TestHandler() { Test.Initialize(); // initialize your mock-object library var yourObj = // mock out the relevant interface Test.Handler<YourMessageHandler>() .WithExternalDependencies(h => h.Dependency = yourObj) // rest of your test } [Test] public void TestHandlerWithConstructorInjection() { Test.Initialize(); // initialize your mock-object library var yourObj = // mock out the relevant interface Test.Handler<YourMessageHandler>(bus => new YourMessageHandler(youObj)) // rest of your test }
One dependency that many message handlers will have is on the bus. That dependency will be filled automatically by the testing infrastructure and does not need to be mocked out in your test.
Other service layer testing functionality
For every method on the bus there is a corresponding test method that sets up an expectation for that call.
