Search code examples
c#.netwpfmvvmasync-ctp

Async CTP, unit testing a ViewModel's async Method


I have a unit test (using MSTest) like so:

[TestMethod]
public void MyTest()
{
    var viewModel = new MyViewModel();
    viewModel.Run();
    //Assert something here
}

Run is an async method that returns void.

Let's say Run is implemented like so:

public async void Run()
{
    //Show a busy indicator here

    try
    {
        var result = await myAsyncModelClass.LongRunningOperation();

        //Use the results here
    }
    finally
    {
        //Hide the busy indicator here
    }
}

myAsyncModelClass.LongRunningOperation(), is itself an async method that returns some Task<T> where T is the result my ViewModel is interested in.

My issue, is that my test is running the Run method asynchronously, so the my assertions are called before the Run methods completes. It is odd, b/c the finally block is never reached when I put a breakpoint, since the assertions fail. How can I keep the Run method synchronous to be able to unit test it?

I have a unit test of myAsyncModelClass.LongRunningOperation() also, but I merely call Task<T>.Wait() since it returns a task. This makes it synchronous when unit testing.

Also, I would like to mention, Run() is invoke by an ICommand magically by an MVVM framework. void may or may not be a require return type, I will have to try it out.


Solution

  • Async methods need a context to "return to". Since MSTests run on the thread pool, by default the async methods all continue on a thread pool thread as well (and do not block the MSTest method).

    Under the (C# Testing) Unit Testing sample (in your Async CTP install directory), there's a type called GeneralThreadAffineContext, which can be used as such:

    [TestMethod]
    public void MyTest()
    {
      MyViewModel viewModel = null;
      GeneralThreadAffineContext.Run(() =>
      {
        viewModel = new MyViewModel();
        viewModel.Run();
      });
      //Assert something here
    }
    

    There are also specific WPF and WinForms contexts, but the thread-affine context should work for general ViewModels (that don't make explicit use of Dispatcher).

    Update 2012-02-05: If you can change your ViewModel method to return Task, then you have another option: the new AsyncUnitTests library. Install that NuGet package, change your TestClass to AsyncTestClass, and your async unit tests can be written much more naturally:

    [TestMethod]
    public async void MyTest()
    {
      MyViewModel viewModel = new MyViewModel();
      await viewModel.Run();
      //Assert something here
    }
    

    Update 2012-09-04: Visual Studio 2012 includes async unit testing, so you don't need the AsyncUnitTests library anymore:

    [TestMethod]
    public async Task MyTest()
    {
      MyViewModel viewModel = new MyViewModel();
      await viewModel.Run();
      //Assert something here
    }