Unit Testing Events with Timeouts
Ever need to perform a unit test on and want to timeout while waiting for an event? Doing a quick google search for, “C# unit test timeout waiting for event” yields interesting mixed results.
Here are a few different methods for completing such a task.
Method 1 – Task.WhenAny
The following uses the System.Threadding.Task.WhenAny method wrapped inside of our async method, WaitUntil(..). Th
using System.Threading;
namespace Test
{
[TestClass]
public class Testing
{
private ManualResetEvent _manualEvent = new ManualResetEvent(false);
[TestMethod]
public void MyMethodTest()
{
await _svc.StartLongRunningTaskAsync();
// Waits 5 seconds until condition is met and checks condition every 25 milliseconds
var timedout = await this.WaitUntil(() => (_countCircuits > 0), 25, 5000);
}
private void OnEventTriggered(object sender, WorkoutEventArgs e)
{
_countCircuits++;
}
public async Task<bool> WaitUntil(Func<bool> condition, int frequency = 25, int timeout = -1)
{
var waitTask = Task.Run(async () =>
{
while (!condition()) await Task.Delay(frequency);
});
return (waitTask != await Task.WhenAny(waitTask, Task.Delay(timeout))) ? false : true;
}
}
}
Method 2 – Synchronous ManualResetEvent
Slightly different implementation without the use of Tasks.
using System.Threading;
namespace Test
{
[TestClass]
public class Testing
{
private ManualResetEvent _manualEvent = new ManualResetEvent(false);
// Our service
// private MyTestService _svc;
[TestInitialize]
public override void CleanupBeforeTest()
{
// _svc = new MyTestService();
// _svc.EventTriggered += OnEventTriggered;
}
[TestCleanup]
public override void CleanupPostTest()
{
// _svc.EventTriggered -= OnEventTriggered;
}
[TestMethod]
public void MyMethodTest()
{
// Configure Event Resetters
_counter = 0;
_manualEvent.Reset();
_manualEvent.WaitOne(3000, false); // Holds in place
// Perform action to trigger eventtests which trigger our event
// _svc.ExecuteEventTrigger();
}
private void OnEventTriggered(object sender, WorkoutEventArgs e)
{
_counter++;
// Get out out of the "_manualEvent.WaitOne(...)"
if (_counter > 3)
_manualEvent.Set();
}
}
}
Method 3 – TaskCompletionSource
private void OnCircuitCompleted(object sender, WorkoutEventArgs e)
{
_countCircuits++;
if (_countCircuits > 3)
tcs?.TrySetResult(true);
}
Method 4 – Task.WhenAny
Asyncronyously awaiting Task.WhenAny allows you to provide a delay as you can see below.
[TestMethod]
public void MyMethodTest()
{
// Do stuff here
if (await Task.WhenAny(task, Task.Delay(3000)) == task)
{
// success
}
else
{
// failure
}
}