This is part of the NServiceBus Upgrade Guide from Version 7 to 8, which also includes the following individual upgrade guides for specific components:
Feature Details
- Upgrading the data bus from version 7 to 8
- Dependency Injection changes
- Upgrade NServiceBus downstreams from Version 7 to 8
- Upgrading message contracts from Version 7 to 8
- Upgrade NServiceBus pipeline extensions from Version 7 to 8
- Transport configuration changes
Transports
- Upgrade AmazonSQS Transport Version 5 to 6
- Azure Service Bus Transport Upgrade Version 2 to 3
- Azure Storage Queues Transport Upgrade Version 10 to 11
- MSMQ Transport Upgrade Version 1 to 2
- RabbitMQ Transport Upgrade Version 7 to 8
- SQL Server Transport Upgrade Version 6 to 7
Persistence
Other
Fluent-style API is not supported
Starting with NServiceBus version 8, the fluent-style testing API is not supported. Tests should be written in an Arrange-Act-Assert (AAA) style. Tests written this way will create the handler or saga to be tested, and call methods on them directly, passing in a testable message handler context that will capture outgoing operations that can be asserted on afterwards.
Testing a handler
To test a handler, create it with the necessary dependencies, then call the Handle
method directly. Pass in an instance of TestableMessageHandlerContext
which will collect all outgoing operations. This context allows customization of metadata about the incoming message, including headers.
An example of the same test written in both the Arrange-Act-Assert style and the fluent style:
8-pre NServiceBus.Testing
[TestFixture]
class ArrangeActAssertHandlerTesting
{
[Test]
public async Task TestHandler()
{
// Arrange
var handler = new RequestMessageHandler();
// Act
var requestMessage = new RequestMessage {String = "hello"};
var messageContext = new TestableMessageHandlerContext();
await handler.Handle(requestMessage, messageContext);
// Assert
Assert.IsTrue(messageContext.RepliedMessages.Any(x =>
x.Message<ResponseMessage>()?.String == "hello"),
"Should send a ResponseMessage reply that echoes the provided string");
}
}
7.x - 7.3 NServiceBus.Testing
[TestFixture]
class FluentHandlerTesting
{
[Test]
public async Task TestHandler()
{
await Test.Handler<RequestMessageHandler>() // Arrange
.ExpectReply<ResponseMessage>( // Assert
check: message =>
{
return message.String == "hello";
})
.OnMessageAsync<RequestMessage>( // Act
initializeMessage: message =>
{
message.String = "hello";
});
}
}
See the handler unit testing documentation for more information.
Testing a saga
To test a saga, create it with the necessary dependencies (including the Data
property), then call the Handle
method directly. Pass in an instance of TestableMessageHandlerContext
which will collect all outgoing operations. This context allows customization of metadata about the incoming message, including headers.
Fluent-style saga tests will often include multiple state changes. Arrange-Act-Assert (AAA) tests should test a single state change in isolation. The state of the saga can be configured manually before each test as part of the Arrange step.
This is an example showing two state changes. The first starts the saga that triggers a timeout, then sends a reply, publishes an event, and sends a message. The second state change happens when the timeout occurs, causing another event to be published, and the saga to be completed. These can be split into two tests (one for each state change) when using the Arrange-Act-Assert style.
8-pre NServiceBus.Testing
[TestFixture]
class ArrangeActAssertSagaTests
{
[Test]
public async Task When_Saga_is_started_by_StartsSaga()
{
// Arrange
var saga = new MySaga
{
Data = new MySaga.SagaData
{
Originator = "Originator"
}
};
// Act
var message = new StartsSaga();
var messageHandlerContext = new TestableMessageHandlerContext();
await saga.Handle(message, messageHandlerContext);
// Assert
Assert.IsTrue(messageHandlerContext.RepliedMessages.Any(x =>
x.Message is MyResponse &&
x.Options.GetDestination() == "Originator"),
"A MyResponse reply should be sent to the originator"
);
Assert.IsTrue(messageHandlerContext.TimeoutMessages.Any(x =>
x.Message is StartsSaga &&
x.Within == TimeSpan.FromDays(7)),
"The StartsSaga message should be deferred for 7 days"
);
Assert.IsTrue(messageHandlerContext.PublishedMessages.Any(x =>
x.Message is MyEvent),
"MyEvent should be published"
);
Assert.IsTrue(messageHandlerContext.SentMessages.Any(x =>
x.Message is MyCommand),
"MyCommand should be sent"
);
}
[Test]
public async Task When_StartsSaga_Timeout_completes()
{
// Arrange
var saga = new MySaga
{
Data = new MySaga.SagaData()
};
// Act
var timeoutMessage = new StartsSaga();
var timeoutHandlerContext = new TestableMessageHandlerContext();
await saga.Timeout(timeoutMessage, timeoutHandlerContext);
// Assert
Assert.IsTrue(timeoutHandlerContext.PublishedMessages.Any(x =>
x.Message is MyOtherEvent),
"MyOtherEvent should be published"
);
Assert.IsTrue(saga.Completed, "Saga should be completed");
}
}
7.x - 7.3 NServiceBus.Testing
[TestFixture]
class FluentSagaTests
{
[Test]
public void TestSagaFluent()
{
Test.Saga<MySaga>()
.ExpectReplyToOriginator<MyResponse>()
.ExpectTimeoutToBeSetIn<StartsSaga>(
check: (state, span) =>
{
return span == TimeSpan.FromDays(7);
})
.ExpectPublish<MyEvent>()
.ExpectSend<MyCommand>()
.When(
sagaIsInvoked: (saga, context) =>
{
return saga.Handle(new StartsSaga(), context);
})
.ExpectPublish<MyOtherEvent>()
.WhenSagaTimesOut()
.ExpectSagaCompleted();
}
}
See the saga unit testing documentation for more information.
Uniform Session
The NServiceBus.
package provided the WithUniformSession
method to configure fluent-style tests to work with IUniformSession
. With AAA-style tests, a new instance of the TestableUniformSession
class can be created and passed to any class with a dependency on IUniformSession
:
[Test]
public async Task TestWithUniformSession()
{
var uniformSession = new TestableUniformSession();
var component = new SharedComponent(uniformSession);
await component.DoSomething();
Assert.AreEqual(1, uniformSession.PublishedMessages.Length);
}
For scenarios where the tested code path both invokes operations of IUniformSession
and a pipeline context like IMessageHandlerContext
, the TestableUniformSession
can be configured to wrap the context:
8-pre NServiceBus.Testing
[Test]
public async Task TestWithMessageHandler()
{
var handlerContext = new TestableMessageHandlerContext();
var uniformSession = new TestableUniformSession(handlerContext);
var sharedComponent = new SharedComponent(uniformSession);
var messageHandler = new SomeMessageHandler(sharedComponent);
// message handler calls SharedComponent within Handle
await messageHandler.Handle(new SomeEvent(), handlerContext);
Assert.AreEqual(1, uniformSession.SentMessages.Length);
// the message handler context and the uniform session share the same state, so these assertions are identical
Assert.AreEqual(1, handlerContext.SentMessages.Length);
}
7.x - 7.3 NServiceBus.Testing
[Test]
public void TestWithMessageHandler()
{
var uniformSession = new TestableUniformSession();
var sharedComponent = new SharedComponent(uniformSession);
var handler = new SomeMessageHandler(sharedComponent);
Test.Handler(handler)
.WithUniformSession(uniformSession)
.ExpectPublish<SomeEvent>()
.OnMessage<SomeMessage>();
}
This approach also works for code that use IUniformSession
and IMessageSession
in the same code path:
[Test]
public async Task TestWithMessageSession()
{
var messageSession = new TestableMessageSession();
var uniformSession = new TestableUniformSession(messageSession);
var sharedComponent = new SharedComponent(uniformSession);
var myService = new MyService(sharedComponent);
// MyService calls SharedComponent within Start
await myService.Start(messageSession);
Assert.AreEqual(1, uniformSession.SentMessages.Length);
// the message session and the uniform session share the same state, so these assertions are identical
Assert.AreEqual(1, messageSession.SentMessages.Length);
}
TestableBehaviorContext
To add delivery constraints to the TestableBehaviorContext
class, use DispatchProperties
instead of AddDeliveryConstraint
on the context bag.
public void UsingDispatchPropertiesInTestableBehaviorContext(NServiceBus.Testing.TestableBehaviorContext context)
{
context.Extensions.Set(new DispatchProperties
{
DelayDeliveryWith = new DelayDeliveryWith(TimeSpan.FromDays(1))
});
}