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 } }