views:

459

answers:

3

I can't seem to find a .NET answer to this problem, which I would have thought would be fairly common.
What is the best pattern for unit testing an asynchronous method?

Obviously I need to call the method and then see if the callback fires, but is there a better way than simply sleeping for a bit and then checking for a flag that is set by the callback? Managing the flags gets a bit messy where there are multiple tests running.

+6  A: 

I typically use an anonymous delegate and a waithandle. FOr example I have a function in my presenter called SetRemoteTableName. When the name is set, it also raises an event. I want to test that event, which is raised asynchronously. The test looks like this:

[TestMethod]
[WorkItem(244)]
[Description("Ensures calling SetRemoteTableName with a valid name works 
              AND raises the RemoteTableNameChange event")]
public void SetRemoteTableNamePositive()
{
  string expected = "TestRemoteTableName";
  string actual = string.Empty;

  AutoResetEvent are = new AutoResetEvent(false);
  SQLCECollectorPresenter presenter = new SQLCECollectorPresenter();
  presenter.RemoteTableNameChange += new EventHandler<GenericEventArg<string>>(
    delegate(object o, GenericEventArg<string> a)
    {
      actual = a.Value;
      are.Set();
    });

  presenter.SetRemoteTableName(expected);
  Assert.IsTrue(are.WaitOne(1000, false), "Event never fired");
  Assert.AreEqual(actual, expected);
}
ctacke
This is fantastic - thank you!
womp
+2  A: 

Split the code so that the logic is in a synchronous bit of code that is called by a thin asynchronous wrapper.

Then most of your unit tests can test the synchronous code.

Douglas Leeder
+1  A: 

is there a better way than simply sleeping for a bit and then checking for a flag that is set by the callback?

Replace the flag with a wait handle. Instead of setting the flag, set the wait handle. Instead of sleeping and then checking whether the flag is set, wait on the wait handle ... and wait with a timeout, so that if you wake up because of timer expiry instead of waking up because the handle on which you were waiting was fired by the callback, then you know that the test failed (i.e. the callback wasn't invoked within the timeout period).

ChrisW