Unit Testing Events with Timeouts

Unit Testing Events with Timeouts

Unit Testing

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

Leave a Reply