Home > front end >  Unit test: wait all threads completion before asserting
Unit test: wait all threads completion before asserting

Time:10-07

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.

CodePudding user response:

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 < threads. 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 < threads. 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));
}

CodePudding user response:

By Nunit testing, I use DelayedConstraint. DelayedConstraint delays the application of another constraint until a certain amount of time has passed. In it's simplest form, it replaces use of a Sleep in the code but it also supports polling, which may allow use of a longer maximum time while still keeping the tests as fast as possible.

DelayedConstraint constraint = Is.True.After(delayInMilliseconds: 100, pollingInterval: 5);
Assert.That(() => _memory.GetMemoryAsync(), Occurs.Exactly(20), expr: constraint);
  • Related