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);