21

Imagine a function like this:

private static ConcurrentList<object> list = new ConcurrentList<object>();
public void Add(object x)
{
   Task.Factory.StartNew(() =>
   {
      list.Add(x); 
   }
}

I don't care WHEN exactly the fentry is added to the list, but i need it to be added in the end ( obviously ;) )

I don't see a way to properly unittest stuff like this without returning any callback-handler or sth. and therefor adding logic that's not required for the program

How would you do it?

Matteo Migliore
  • 925
  • 8
  • 22
David
  • 2,551
  • 3
  • 34
  • 62

5 Answers5

22

One way to do this is to make your type configurable such that it takes a TaskScheduler instance.

public MyCollection(TaskScheduler scheduler) {
  this.taskFactory = new TaskFactory(scheduler);
}

public void Add(object x) {
  taskFactory.StartNew(() => {
    list.Add(x);
  });
}

Now in your unit tests what you can do is create a testable version of TaskScheduler. This is an abstract class which is designed to be configurable. Simple have the schedule function add the items into a queue and then add a function to manually do all of the queue items "now". Then your unit test can look like this

var scheduler = new TestableScheduler();
var collection = new MyCollection(scehduler);
collection.Add(42);
scheduler.RunAll();
Assert.IsTrue(collection.Contains(42));

Example implementation of TestableScehduler

class TestableScheduler : TaskScheduler {
  private Queue<Task> m_taskQueue = new Queue<Task>();

  protected override IEnumerable<Task> GetScheduledTasks() {
    return m_taskQueue;
  }

  protected override void QueueTask(Task task) {
    m_taskQueue.Enqueue(task);
  }

  protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
    task.RunSynchronously();
  }

  public void RunAll() {
    while (m_taskQueue.Count > 0) {
      m_taskQueue.Dequeue().RunSynchronously();
    }
  }
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • Aren't you concerned about hiding potential race conditions by using an approach like this? Or do you feel that testing the multithreaded behaviour of code doesn't fit under the unit test umbrella? – Nicole Calinoiu Oct 21 '11 at 17:47
  • 1
    @NicoleCalinoiu this approach is only mean for testing the specific case that when task X completes the result is in the collection. To test for race conditions I would use a much different version of this type. Both cases need to be tested for sure. – JaredPar Oct 21 '11 at 17:48
  • Guess you're right... i was hoping that there was a "simpler solution" than to DI everything thread-related.. :( – David Oct 21 '11 at 18:38
  • @JaredPar I tried your TaskScheduler but it doesn't work (the TryXxx method must return a bool that doesn't) but the real problem is that I obtain an InvalidOperationException "RunSynchronously may not be called on a task that was already started.". – Matteo Migliore Sep 27 '12 at 06:47
  • 2
    The principle is sound, but this implementation doesn't actually work. – GarethD Sep 17 '13 at 09:49
8

The solution that worked for me was to send the TaskScheduler as a dependency to the code I want to unit test (e.g.

MyClass(TaskScheduler asyncScheduler, TaskScheduler guiScheduler)

Where asyncScheduler is used to schedule tasks that run on worker threads (blocking calls) and guiScheduler is used to schedule tasks that should run on GUI (non blocking calls).

In the unit test, I would then inject a specific schedulers, i.e. CurrentThreadTaskScheduler instances. CurrentThreadTaskScheduler is a scheduler implementation that runs the tasks immediately, instead of queuing them.

You can find the implementation in the Microsoft Samples for Parallel Programming here.

I'll paste the code for quick reference:

/// <summary>Provides a task scheduler that runs tasks on the current thread.</summary>
public sealed class CurrentThreadTaskScheduler : TaskScheduler
{
    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
    /// <param name="task">The task to be executed.</param>
    protected override void QueueTask(Task task)
    {
        TryExecuteTask(task);
    }

    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
    /// <param name="task">The task to be executed.</param>
    /// <param name="taskWasPreviouslyQueued">Whether the Task was previously queued to the scheduler.</param>
    /// <returns>True if the Task was successfully executed; otherwise, false.</returns>
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return TryExecuteTask(task);
    }

    /// <summary>Gets the Tasks currently scheduled to this scheduler.</summary>
    /// <returns>An empty enumerable, as Tasks are never queued, only executed.</returns>
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return Enumerable.Empty<Task>();
    }

    /// <summary>Gets the maximum degree of parallelism for this scheduler.</summary>
    public override int MaximumConcurrencyLevel { get { return 1; } }
}
Alex G
  • 718
  • 1
  • 7
  • 9
  • ... or manually override the default task scheduler in TPL to avoid introducing this dependency in your code: http://www.jarrodstormo.com/2012/02/05/unit-testing-when-using-the-tpl/ – Dunc Jul 22 '13 at 11:55
  • 1
    Anyone taking this approach might consider injecting something a bit more expandable e.g. ISchedulerService which can also carry other types of scheduler. I inject this interface which contains schedulers for Rx as well as TPL schedulers so I can use them within my code. – The Senator Apr 22 '18 at 17:30
  • @TheSenator would you mind to share your example how this interface might looks like or may be implementation as well if that is not too custom/NDA-ed? Particularly interested in Rx part of it. – Sevenate Apr 05 '22 at 00:05
1

A colleague of mine and I are building a unit testing framework which addresses TPL and Rx testing, and there is a class which you could leverage to replace the default TaskScheduler in a testing scenario, so that you don't need to modify your method signatures. The project itself isn't published yet, but you can browse the file here nonetheless:

https://github.com/Testeroids/Testeroids/blob/master/solution/src/app/Testeroids/TplTestPlatformHelper.cs

The work of setting up the task scheduler is done in TplContextAspectAttribute.cs.

Pedro Pombeiro
  • 1,654
  • 13
  • 14
0

What about making a public property for the list?

public ConcurrentList<object> List { get; set; }

or maybe make it a public field when in DEBUG build:

#if DEBUG
public static ConcurrentList<object> list = new ConcurrentList<object>();
#else
private static ConcurrentList<object> list = new ConcurrentList<object>();
#endif
Greg Finzer
  • 6,714
  • 21
  • 80
  • 125
-1

For at least most simple-ish cases, I like to use an "expiring" assertion for this sort of thing. e.g.:

YourCollection sut = new YourCollection();

object newItem = new object();
sut.Add(newItem);

EventualAssert.IsTrue(() => sut.Contains(newItem), TimeSpan.FromSeconds(2));

where EventualAssert.IsTrue() looks something like this:

public static void IsTrue(Func<bool> condition, TimeSpan timeout)
{
    if (!SpinWait.SpinUntil(condition, timeout))
    {
        Assert.IsTrue(condition());
    }
}

I also generally add an override with a default timeout which I use for most of my tests, but ymmv...

rileymcdowell
  • 590
  • 1
  • 4
  • 15
Nicole Calinoiu
  • 20,843
  • 2
  • 44
  • 49