Introduction
This tip illustrates how the .NET Task.Factory.StartNew(Task)
method can be injected as a dependency and mocked for testing. The sample project includes a Test project using nUnit and Rhino Mocks to show the code working, but any unit test and mocking framework can be used to achieve the same result.
Background
On a recent project, I had the requirement to execute calls to an external email subscription service using a separate thread. We did this to improve the user experience. The user didn't need to know if the email trigger was successful or not, so there was no point making the user wait. Also, because we were calling a third party web service, we couldn't guarantee the response time. To solve this, I used the Task.Factory
class to start a new task to send the service message to the external web service, but not wait for the response (effectively a fire and forget). The task was responsible for logging any errors.
This all worked well in a proof of concept, but when I started coding the real solution I immediately ran into a problem. I'm a big fan of test driven development, but the call to Task.Factory.StartNew(Action)
was making the method hard to test. After trawling the net for a solution, I stumbled across various articles and blogs about using a wrapping interface (some called it a factory, but the final solution I chose is more like a proxy). That solved the problem of injecting the Task.Factory.StartNew(Task)
as a dependency. I then also found a solution to use mocks to verify that the correct action is being called. So I can't take credit for the solution below, but I thought it useful to present the two parts together, as they form the total solution (or did in my case).
Creating a Proxy
The first step to making the Task.Factory.StartNew(Task) injectable is to wrap it with our own interface and accompanying implementation. This technique is similar to my previous article on the Proxy Pattern (see http://www.codeproject.com/Articles/664496/Using-the-Proxy-Pattern-to-inject-the-uninjectable). Our simple interface exposes a method to start a new task on a separate thread.
public interface ITaskFactory
{
void StartTask(Action action);
}
Next we implement the interface.
public class TaskFactory : ITaskFactory
{
public void StartTask(Action action)
{
Task.Factory.StartNew(action);
}
}
Injecting the Proxy
Now that we have an interface for our task factory, we can inject it into a class that needs it. The following class also has a secondary class injected. The secondary class and method (ClassTwo.FireAndForget(int)
) will be used as the action for the new task.
public class ClassOne : IClassOne
{
private readonly ITaskFactory _taskFactory;
private readonly IClassTwo _classTwo;
public ClassOne(ITaskFactory taskFactory, IClassTwo classTwo)
{
_taskFactory = taskFactory;
_classTwo = classTwo;
}
public void CallAsync(int number)
{
_taskFactory.StartTask(() => _classTwo.FireAndForget(number));
Console.WriteLine("Response from ClassOne with {0} on thread {1}.", number,
Thread.CurrentThread.ManagedThreadId);
}
}
For illustration purposes only, I've added some Console.WriteLine
statements that represent the work that the method is doing after the task is started.
Unit Testing
Now I want to unit test ClassOne.CallAsync
. I can make use of the ITaskFactory
interface to mock the StartTask
method. I also want to test that ClassTwo.FireAndForget(int)
is the action that will be executed in the new task and that the parameter was passed into correctly. The code below shows the full test class to test these two scenarios.
[TestFixture]
public class ClassOneTest
{
#region SetUp
private IClassOne _classOne;
private ITaskFactory _taskFactory;
private IClassTwo _classTwo;
[SetUp]
public void SetUp()
{
_taskFactory = MockRepository.GenerateStub<itaskfactory>();
_classTwo = MockRepository.GenerateStub<iclasstwo>();
_classOne = new ClassOne(_taskFactory, _classTwo);
}
#endregion
#region CallAsync
[Test]
public void CallAsyncCallsTaskFactory()
{
_classOne.CallAsync(1);
_taskFactory.AssertWasCalled(t=>t.StartTask(Arg<action>.Is.Anything));
}
[Test]
public void CallAsyncCallsClassTwoByTaskFactory()
{
Action actionExpected = null;
_taskFactory.Expect(x => x.StartTask(Arg<action>.Is.Anything))
.WhenCalled(y => actionExpected = (Action)y.Arguments[0]);
_classOne.CallAsync(1);
actionExpected.Invoke();
_classTwo.AssertWasCalled(e => e.FireAndForget(Arg<int>.Is.Equal(1)));
}
#endregion
}
The SetUp()
method creates the mock objects for the dependencies. As mentioned above, we're using Rhino Mocks as our mocking framework. The first test, CallAsyncCallsTaskFactory()
, verifies (asserts) that a new task is started when CallAsync
is executed. The second test verifies that FireAndForget
is the method that will be executed by taking advantage of Rhino Mocks WhenCalled
extension. This allows us to retrieve the first argument passed into the TaskFactory
mock (which is the action being executed).
The action is the FireAndForget
method on the mock we setup for the ClassTwo
object, so when we call Invoke()
on that action, the mock will allow us to assert that the FireAndForget
method is executed and also verify that the parameter passed into it is correct.
History
- 7 January 2014: Initial version