Problem
You’ve created an orchestration using the Service Bus Durable Task Framework that makes use of several tasks, but you want to be able to test out each task in isolation.
Solution
The Durable Task Test Framework provides you with a utility class that makes creating and executing a new orchestration with a single task painless and easy. Whilst this isn’t a unit test by any stretch of the imagination, the MSTest framework is perfect for running your tasks one by one and making sure everything is as it should be.
How it Works
Creating and configuring an orchestration is relatively simple, but involves quite a few lines of code. If you’re just testing a single TaskActivity
, there’s no point in doing that over and over again, so I created a simple helper class to take care of the grunt work!
Test Orchestration
To be able to run our task activity we need an orchestration to run it in. As we’re only dealing with a single activity, we know that the input and output types of the orchestration will be the same as the activity. With a few generic parameters, we have a simple TaskOrchestration
implementation that can run a single activity.
namespace DurableTaskTestFramework { using Microsoft.Practices.Unity; using Microsoft.ServiceBus.DurableTask; using System.Threading.Tasks; using UnityDurableTaskFramework; public class TestOrchestration<TResult, TInput, TTaskType> : TaskOrchestration<TResult, TInput> where TTaskType : TaskActivity { public TestOrchestration() { } public override async Task<TResult> RunTask(OrchestrationContext context, TInput input) { return await context.ScheduleTask<TResult>(typeof(TTaskType), input); } } }
There’s really not much to say here. The generic type parameters take care of everything really.
TResult
is the type of the return value from theTaskActivity
being run and thus the return type of the orchestration.TInput
is the type of the input value into theTaskActivity
and thus the input to the orchestration.TTaskType
is the type of theTaskActivity
you want to run.
Durable Task Helper
Now that we have a generic orchestration that can run any TaskActivity
, we need to configure a task client, hub and run the orchestration. That’s where the DurableTaskHelper
comes in.
The helper is so helpful that it only needs two methods to use it. Well three including the constructor. We’ll take a look at each method in turn rather than filling the page with lines after lines of code.
Getting all nice and set up
The constructor takes care of the task hub name and your service bus connection string.
public DurableTaskHelper(string taskHubName, string connectionString) { this.taskHubName = taskHubName; this.connectionString = connectionString; }
The SetupTaskHub
method is the lengthiest method of the class, but from my first post in the series, should be familiar with the addition of some generic type parameters which are the same as above. Got to love those little things!
public TaskHubClient SetupTaskHub<TResult, TInput, TTaskType>(TInput input, IUnityContainer unityContainer) where TTaskType : TaskActivity { this.client = new TaskHubClient(this.taskHubName, this.connectionString); this.worker = new TaskHubWorker(this.taskHubName, this.connectionString); this.worker.CreateHub(); var orchestration = this.client.CreateOrchestrationInstance(typeof(TestOrchestration<TResult, TInput, TTaskType>), input); var orchestrationFactory = new UnityObjectCreator<TaskOrchestration>( unityContainer, typeof(TestOrchestration<TResult, TInput, TTaskType>)); var taskFactory = new UnityObjectCreator<TaskActivity>(unityContainer, typeof(TTaskType)); this.worker.AddTaskOrchestrations(orchestrationFactory); this.worker.AddTaskActivities(taskFactory); return this.client; }
The method returns the TaskHubClient
that was created to allow you to raise events etc if needs be. You’ll see that I make use of the UnityObjectCreator
from before and the new TestOrchestration
. There shouldn’t be any surprises here as this is all standard stuff.
Running the orchestration
Last, and by no means least, we have the RunOrchestration
method. All this does is start off the worker, wait for the client to finish, then stop the worker again. Couldn’t be simpler.
public void RunOrchestration() { this.worker.Start(); while (this.client.GetPendingOrchestrationsCount() > 0) { Thread.Sleep(500); } this.worker.Stop(true); }
The orchestration is run on another thread, so we just sit and wait here until it finishes.
Putting it to use
Now to make all of this stuff actually do something! As mentioned before, we’re not creating a unit test in the strict sense, we’re just making use of an existing framework to allow us to run our testing. Continuing on from the simple banking example in the last posts, we’ll write a test that runs the CreditAccountTask
and makes sure everything is working well.
Here’s what the test method looks like:
[TestMethod] public void CreditAccountSuccessful() { var unityContainer = new UnityContainer(); var bankAccount = new BankAccount(); unityContainer.RegisterInstance<IBankAccount>(bankAccount, new ContainerControlledLifetimeManager()); var input = new Transaction() { SourceBankAccount = "123456", TargetBankAccount = "654321", Amount = 100M }; var durableTaskHelper = new DurableTaskHelper("CreditAccountTaskTest", this.connectionString); var taskHubClient = durableTaskHelper.SetupTaskHub<bool, Transaction, CreditAccountTask>(input, unityContainer); // Just to show you what the balances are before we run the orchestration. Assert.AreEqual(100000M, bankAccount.GetBalance("123456")); Assert.AreEqual(0M, bankAccount.GetBalance("654321")); durableTaskHelper.RunOrchestration(); // We didn't debit anything, so the first account should be untouched... Assert.AreEqual(100000M, bankAccount.GetBalance("123456")); // Check that the transaction amount was credited... Assert.AreEqual(input.Amount, bankAccount.GetBalance("654321")); }
With just a few extra lines of code, we’re able to run our activity on its own! Great hey! Of course this is very trivial and would be extended as you wish. The most valuable thing here is that we have a very simple framework for running our activities in isolation, with full support for our unity container! Now that’s cool. This allows me to mock out objects in the container using NSubstitute and ensure that my more complex activities are working well with very little fuss.
The code is available for download as a Visual Studio 2012 solution, built against .Net 4.5 and ServiceBus.DurableTask Framework 0.2.0.2.
Note: To make this work, you’ll need an Azure subscription and a service bus connection string. Edit the app.config in the project to set your subscription connection string. At present, there isn’t any way to emulate the service bus locally.
Also make sure you have the NuGet package manager installed. The source archive doesn’t contain all the dependencies to save on storage space, but when you first build the project they should be downloaded automatically.