Search code examples
c#unit-testingasync-awaitjustmock

Unit test: wait all threads completion before asserting


Inside a test I am creating multiple threads, to test racing conditions, I need the threads start at the same time, so far so good. But when I assert the number of times the method has been executed, if fails because it is not waiting until all the threads have finished.

[Test]
public void Test_Racing_Condition()
{
   //Arrange & Act
   var threads = new Thread[20];
   for (int i = 0; i < threads.Length; i++)
   {
      thread[i] = new Thread(async () => await _memory.GetMemoryAsync());
   }
   foreach (Thread thread in threads)
   {
       thread.Start();
   }
   foreach (var thread in threads)
   {
       thread.Join();
   }

   //Assert
   Mock.Assert(() => _memory.GetMemoryAsync(), Occurs.Exactly(20));
}

How can I force the test to wait for all the threads before asserting?

The issue I have is that the assertion is being done, before the threads finish, therefore the test fails.


Solution

  • The problem here is that you're using async void in here: new Thread(async () => await _memory.GetMemoryAsync()). as soon as _memory.GetMemoryAsync() awaits the first uncompleted await, the method will return and the thread finishes its work.

    Try this instead:

    [Test]
    public async Task Test_Racing_Condition()
    {
       //Arrange & Act
       var tasks = new Task[20];
       for (int i = 0; i < tasks.Length; i++)
       {
          // Use Task.Run to return immediately.
          // Not necessary in production code.
          tasks[i] = Task.Run(_memory.GetMemoryAsync);
       }
       await Task.WhenAll(tasks);
    
       //Assert
       Mock.Assert(() => _memory.GetMemoryAsync(), Occurs.Exactly(20));
    }
    

    If you really need them to start closest to each other as possible:

    [Test]
    public async Task Test_Racing_Condition()
    {
       //Arrange
       using (var tcs = new TaskCompletionSource<object>())
       {
          var tasks = new Task[20];
          for (int i = 0; i < tasks. Length; i++)
          {
             // Use Task.Run to return immediately.
             // Not necessary in production code.
             tasks[i] = Task.Run(async () =>
                {
                   await tcs.Task;
                   await _memory.GetMemoryAsync();
                });
          }
    
          // Act
          tcs.SetResult(null);
       }
    
       await Task.WhenAll(tasks);
    
       //Assert
       Mock.Assert(() => _memory.GetMemoryAsync(), Occurs.Exactly(20));
    }