Unit Testing Async

Perhaps you don’t want to go to the trouble of implementing the IAsyncResult pattern to accomplish some asynchronous unit of work. The async and await keywords added to C# in the .NET 4.5 framework are handy when you want to save time by using task-based asynchronous processing, or quickly turn a synchronous method into an asynchronous one. So, you’ve marked your method as async and placed the await keyword, within your new async method, on the proper line. Now, how do you unit test it?

Definitions

All definition content has been paraphrased from MSDN and the Task-based Asynchronous Pattern document, TAP.docx, which at the time of this writing can be found here: http://www.microsoft.com/en-us/download/confirmation.aspx?id=19957

async: A method modifier that signals to the .NET language compiler that the marked method will run within its own thread, and that execution cannot continue past the point until the awaited asynchronous process is finished. Meanwhile, control is returned to the caller of the async method.

await: An operator that suspends the execution of the method to which it applies. When called, an asynchronous method synchronously executes the body of the function up until the first await expression on an awaitable instance that is not yet completed, at which point the invocation returns to the caller.

ManualResetEvent: A class which provides methods and functionality for one or more waiting threads to be notified, or signaled, that an event has occurred.

Procedure

The example solution, which can be downloaded by clicking the example link below, was created first by selecting the Console Application Project Template.  The structure of the full solution is shown below.  

Testing Async Example Solution Structure

As you can see, there are two projects, one is the console project already mentioned, and the other is a unit test. You will need a new class in the console project prior to testing it. This class, ServiceInvokeWrapper, is shown below.

The ServiceInvokeWrapper class contains the method we will test. It's purpose is to ...

  1. Provide a method for handling the invocation of a simple, and publicly available WCF service that performs simple calculations and returns the result of those calculations.
  2. Contain the async method we will test, AddWrapperAsync, that invokes the add method asynchronously.
  3. Provide a method that simulates work being performed in some lengthy interval.

Here is the class:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;

namespace AsyncAwaitTestConsole
{
    public class ServiceInvokeWrapper
    {
        /// <summary>
        /// Handles service communcation for the delegate passed in and returns the service method invocation result.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="func"></param>
        /// <returns></returns>
        private T WithTryCatch<T>(Func<CalcServiceClient.CalculatorClient, T> func)
        {
            long start = DateTime.Now.Ticks;
            CalcServiceClient.CalculatorClient client = null;
   
            try
            {
                client = new CalcServiceClient.CalculatorClient();

                LengthyWork();

                return func(client);
            }
            catch (CommunicationException serviceModelCommunicationException)
            {
                System.Reflection.MethodBase thisMethod = System.Reflection.MethodInfo.GetCurrentMethod();
                Console.WriteLine("{0}.{1}: A communication error occurred - {2}", thisMethod.DeclaringType, thisMethod.Name, serviceModelCommunicationException.Message);

                throw;
            }
            catch (Exception e)
            {
                Console.WriteLine("Unhandled Error occurred: {0}", e);

                throw;
            }
            finally
            {
                if (client != null)
                {
                    client.Abort();
                }

                string name = System.Reflection.MethodBase.GetCurrentMethod().Name;
                TimeSpan span = TimeSpan.FromTicks(DateTime.Now.Ticks - start);
                Console.WriteLine("Time taken (ms) for {0} call={1}", name, span.TotalMilliseconds);

            }
        }

        public async Task<float> AddWrapperAsync(float num1, float num2)
        {
            float finalResult = await WithTryCatch(delegate(CalcServiceClient.CalculatorClient client)
            {
                return Task.FromResult(client.add(num1, num2));
            });

            LengthyWork();

            return finalResult;
        }

        public void LengthyWork()
        {
            int workResult = 0;

            for (int i = 0; i < 100000; i++)
            {
                for (int j = 0; j < 10000; j++)
                {
                    workResult += i + j;
                }
            }

            Console.WriteLine("Finished doing lengthy work.");
        }
    }
}

 

Once this class is coded and compiling properly, go ahead and add the second project from project template Unit Test Project. This will get you a class identified as a TestClass, and a method identified as a TestMethod. This method is where you will add the following code to test the async method. The full implementation of the class is shown below.

 

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AsyncAwaitUnitTest
{
    /// <summary>
    /// The async and await keywords added to C# in the .NET 4.5 framework are handy when you want to create an asynchronous unit of work or quickly turn a synchronous method into an asynchronous one.  So, you’ve marked your method as async and placed the await keyword, within your new async method, on the proper line.  Now, how do you unit test it?  
    /// </summary>
    [TestClass]
    public class CalcMethodInvokerTest
    {
        [TestMethod]
        public void AddWrapperTest()
        {
            bool successFlag = false;
            float floatResult;

            ManualResetEvent waiter = new ManualResetEvent(false);
            AsyncAwaitTestConsole.ServiceInvokeWrapper serviceInvokeWrapper = new AsyncAwaitTestConsole.ServiceInvokeWrapper();

            serviceInvokeWrapper.AddWrapperAsync(14f, 21f).ContinueWith(x =>
                {
                    floatResult = x.Result;
                    Console.WriteLine("The result of 14 + 21 is: {0}", floatResult);

                    successFlag = true;
                    waiter.Set();
                }
            );

            waiter.WaitOne();

            Assert.AreEqual(successFlag, true);

        }
    }
}

 

The use of a ManualResetEvent object is important here. The call to WaitOne blocks the current thread until the Set method is called, signalling to the ManualResetEvent object that it can stop blocking the current executing thread and continue with executing the code after the line where WaitOne is called. You can run the test and notice that is passes due to the successFlag variable containing a value of true. Comment out all of the ManualResetEvent object usage, however, and notice the test fails. This is async in .NET 4.5 at work! That is, the delegate executed after the async work is completed, will execute sometime after the test result assertion, if the primary thread of execution isn't abandoned altogether due to the test method completing at that point. However, telling the test thread to wait for a while until the delegate, facilitated by ContinueWith, is executed, and then determine the outcome of the test is much more preferrable to a race between the async thread and the test thread.

 

Unit Test Outcome in Visual Studio 2012Outcome of the unit test using the ManualResetEvent object.

So, the use of ManualResetEvent is an effective way to unit test your async method. This way, you won't have to go through the ceremony of installing your application or service in a test environment to know if your new async method is functioning as you would expect it to function within a simple unit test. Happy coding!

Download the example.