public class ClassToTest
{
private readonly IMessageBus _messageBus;
private CancellationTokenSource _cancellationTokenSource;
public event EventHandler OnNewMessage;
public ClassToTest(IMessageBus messageBus)
{
_messageBus = messageBus;
}
public void Start()
{
_cancellationTokenSource = new CancellationTokenSource();
Task.Run(() =>
{
var message = _messageBus.GetNextMessage();
// Do work
if (OnNewMessage != null)
{
OnNewMessage(this, EventArgs.Empty);
}
}, _cancellationTokenSource.Token);
}
public void Stop()
{
if (_cancellationTokenSource != null)
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource = null;
}
}
}
When faced with similar a code a developer has a “tiny” problem – how to force the code inside Task.Run to execute before the end of the test is reached.[TestMethod]
public void BadTest()
{
var fakeMessageBus = A.Fake<IMessageBus>();
var wasCalled = false;
var cut = new ClassToTest(fakeMessageBus);
cut.OnNewMessage += (sender, args) => wasCalled = true;
cut.Start();
Assert.IsTrue(wasCalled);
}
[TestMethod]
public void WorseTest()
{
var fakeMessageBus = A.Fake<IMessageBus>();
var wasCalled = false;
var cut = new ClassToTest(fakeMessageBus);
cut.OnNewMessage += (sender, args) => wasCalled = true;
cut.Start();
Thread.Sleep(1000);
Assert.IsTrue(wasCalled);
}
The problem is that by adding a sleep we managed to achieve an inconsistent test while also making sure that the test would run a staggering 1 second – if that doesn’t sound like a lot think how much time 60 of such tests would run, what about 1000 tests each taking a single second?public class CurrentThreadTaskScheduler : TaskScheduler
{
protected override void QueueTask(Task task)
{
TryExecuteTask(task);
}
protected override bool TryExecuteTaskInline(
Task task,
bool taskWasPreviouslyQueued)
{
return TryExecuteTask(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
public override int MaximumConcurrencyLevel { get { return 1; } }
}
Now we need to pass that scheduler to the executing task either by using a wrapper object or dependency injection.public void Start()
{
_cancellationTokenSource = new CancellationTokenSource();
Task.Factory.StartNew(() =>
{
var message = _messageBus.GetNextMessage();
// Do work
if (OnNewMessage != null)
{
OnNewMessage(this, EventArgs.Empty);
}
}, _cancellationTokenSource.Token,
TaskCreationOptions.None,
TaskScheduler.Current);
}
And now I could write the following test that would pass every single time.[TestMethod]
public void GoodTest()
{
var fakeMessageBus = A.Fake<IMessageBus>();
var wasCalled = false;
Task.Factory.StartNew(() =>
{
var cut = new ClassToTest(fakeMessageBus);
cut.OnNewMessage += (sender, args) => wasCalled = true;
cut.Start();
}, CancellationToken.None,
TaskCreationOptions.None,
new CurrentThreadTaskScheduler());
Assert.IsTrue(wasCalled);
}
Another benefit is that I could also test for negative conditions (something didn’t happen) since now all of the code runs synchronously.Labels: C#, Concurrent, Tips and Tricks, Unit tests