.netasync-ctpvisual-studio-2012

How to test exceptions in async methods


I'm playing around with the Visual Studio 11 Beta.

Given this code:

namespace KC.DataAccess.Global
{
    /// <summary>Global methods for SQL access</summary>
    public static class SQL
    {    
        public async static void ExecuteNonQuery(string ConnStr, string Query)
        {
            if (string.IsNullOrEmpty(ConnStr)) throw new ArgumentNullException("ConnStr");
            if (string.IsNullOrEmpty(Query)) throw new ArgumentNullException("Query");
            SqlConnection conn = new SqlConnection(ConnStr);
            SqlCommand cmd = PrepSqlConnection(ref conn, Query);
            Exception exc = null;
            for (int i = 0; i < 3; i++)
                try { await Task.Run(() => cmd.ExecuteNonQuery()); break; }
                catch (Exception ex) { Thread.Sleep(50); exc = ex; }
            if (exc != null) throw new ApplicationException("Command failed after maximum attempts", exc);
            conn.Close();
            conn.Dispose();
         }
    } 
} 

As it is an async method, the exceptions do not seem to bubble up to the calling method. I have test cases which therefore fail:

using Target = KC.DataAccess.Global.SQL;
[TestMethod]
[TestCategory("Unit")]
[ExpectedException(typeof(ArgumentNullException))]
public void ExecuteNonQueryFail1()
{
    Target.ExecuteNonQuery(null, "select 1");
}

The validation part of ExecuteNonQuery is clearly throwing an exception in this case, and I see it throw when I debug it.

I have changed the test method to an async and the syntax to await Task.Run(() => Target.ExecuteNonQuery()), to no avail.

Questions:


Solution

  • Since your method returns void, there is no way how the exception could propagate to the calling code. If you change the return type to Task, you can now observe the exception, but you have to do that explicitly.

    I think that the best way to modify your calling code is just to call Wait(). If the code in the Task threw an exception, Wait() will throw an AggregateException that will contain the original exception.