Skip to main content

The new and improved NServiceBus testing framework

This post is part of a series describing the improvements in NServiceBus 6.

Tests are the lifeblood of many large codebases. They protect you from introducing bugs and, in some cases, are instrumental in your code’s design. Because of this, the maintenance of those tests is every bit as crucial as the underlying code being tested. Like the rest of the project, your tests should be clear, concise, and consistent with your code style. Otherwise, tests might fall into disrepair and end up in a large bucket called technical debt, never to be heard from again.

With NServiceBus 6, we’ve made it even easier to build tests that follow established conventions and that align more closely with your existing code. Before we see what’s new, let’s take a quick look at how tests for NServiceBus handlers were written in previous versions.

🔗Testing with the fluent API

Let’s say we want to test to make sure that when a handler receives a StartProcess event, it will publish the Started event but not send a ContinueProcess command. Using the fluent API in NServiceBus 5, we initialize our handler in the Test entry class, set up what we expect to happen, then trigger the action. Behind the scenes, the framework ensures all the expectations have been met and reports back if they haven’t:

[Test]
public void TestHandler()
{
    Test.Handler(new SampleHandler(new SomeDependency()))
        .ExpectPublish<Started>(msg => msg.SomeId == 123)
        .ExpectNotSend<ContinueProcess>(msg => true)
        .OnMessage(new StartProcess { SomeId = 123 });
}

Although this style of unit testing is very compact and readable, the fluent API does introduce some challenges. For example, with all API calls rolled into a single statement, it can be difficult to step through a test and pinpoint exactly where it is failing.

While we still support that fluent API, we introduced another way of testing your handlers in NServiceBus 6. Let’s take a look at it now.

🔗Tests that match your style

Starting with NServiceBus 6, the new version of the testing framework has less ceremony and doesn’t force you to use the fluent API. Because of this, you can write tests for your message handlers that can adapt better to your existing test suite.

For example, you might be familiar with the Arrange-Act-Assert pattern of writing unit tests. With this testing pattern, we arrange the subject under test with all required parameters and dependencies. Then we act by invoking the method we want to test. Finally, we assert that everything worked as expected. With NServiceBus 6, it’s easy to follow this pattern to test your message handlers, sagas, and other custom NServiceBus components.

Let’s rewrite our previous example using the Arrange-Act-Assert approach:

[Test]
public async Task TestHandlerAAA()
{
    // Arrange
    var handler = new SampleHandler(new SomeDependency());
    var context = new NServiceBus.Testing.TestableMessageHandlerContext();

    // Act
    await handler.Handle(new StartMsg {SomeId = 123}, context);

    // Assert
    Assert.AreEqual(1, context.PublishedMessages.Length);
    Assert.AreEqual(0, context.SentMessages.Length);
    var started = context.PublishedMessages[0].Message as Started;
    Assert.AreEqual("test", started.SomeId);
}

While this may be more verbose than it would be with the fluent API, it’s clear to see exactly what we’re testing: the Handle method on our SampleHandler. Furthermore, we can more easily see what conditions must be met in order for this test to pass. And if it fails, we’ll know precisely which condition caused the failure.

You may have noticed in the Act stage above that message handlers now receive an additional context parameter. From the message handler, the context parameter allows us to send or publish messages, as well as access message headers. With the testable implementations provided by the NServiceBus.Testing package, we can inspect the results of these operations to ensure our handler behaved correctly.

Another advantage of testing with NServiceBus 6 is that you can extend it to be even more expressive. For example, we previously saw how we could make assertions on various elements of the SentMessages and PublishedMessages collections. These collections can also be extended to be more intention-revealing. We could, for instance, change the Assert.AreEqual(0, context.SentMessages.Length); above to context.SentMessages.Should().BeEmpty(); by using the FluentAssertions library.

🔗Do I have to rewrite my old tests?

Not at all. We recognize that it’s not practical to migrate an entire suite of fully functioning tests from one framework to another so your existing tests will continue to work as is. The new approach is not intended to replace your existing NServiceBus tests but to allow you to write new tests that match the conventions and style of your current non-NServiceBus tests.

The advantage of having both styles of testing available is that it removes the pressure of upgrading your tests from previous versions of NServiceBus to version 6. You can migrate your tests from one approach to the other gradually. Over time, your tests will become more maintainable, as developers old and new can follow the same conventions for the entire test suite.

🔗Summary

NServiceBus 6 makes writing tests for your NServiceBus message handlers and other components easier than ever. They can be written more expressively with the same conventions and libraries used in the rest of your code, making them easier to understand and more maintainable. We can’t wait for you to try it out!

The new testing library is part of NServiceBus Version 6. For some examples of testing with NServiceBus 6, check out our unit testing sample.

Give it a try – your tests will thank you for it.


About the author: Tim Bussmann is a developer at Particular Software. When not at his computer, he enjoys hiking in the mountains in his native Switzerland.

Share on Twitter
Don't miss a thing. Sign up today and we'll send you an email when new posts come out.
Thank you for subscribing. We'll be in touch soon.
 
We collect and use this information in accordance with our privacy policy.