2

I have created a sample WPF MVVM project which I now want to Unit test. The viewmodels load the data asynchronously in the constructor:

public class CustomerOverviewViewModel
{
   public CustomerOverviewViewModel()
   {
       var t = LoadFullCustomerListAsync();
   }
   public async Task LoadFullCustomerListAsync()
   {
      List<BL_acc> customers = await Task.Run(() => // Query from db);
   }
}

In WPF, this works like a charm. When I want to create a unit test for this viewmodel, I create the object by calling its default constructor:

  [TestMethod]
  public void Test()
  {
      customerOverviewViewModel = new CustomerOverviewViewModel();
      // Do the test
  }

However, the unit test has no way of knowing when the async method is finished. Can this be fixed using constructor initialization or should I use a different pattern?

Edit

The unit tests don't need the async loaded information, they just need an instance of the class to test the methods. It just seems like a lot of extra work to use another initialization method just for my unit tests.

All the unit tests succeed but they sometimes throw an error that multiple threads try to access the same context (which disappears when the data is not loaded async):

The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.

noseratio
  • 59,932
  • 34
  • 208
  • 486
Bruno V
  • 1,721
  • 1
  • 14
  • 32
  • 2
    Why does the unit test need to know that the async bit has finished? If it's to protect your class from being used before it's fully constructed, then this protection should exist in your class anyway. If it's because you're trying to test that an async call has been started then mocking the database interaction that the task is starting may be the way forward. I think you need to provide a bit more context if possible. – forsvarir Jul 08 '15 at 10:08
  • I've added some more information to the question. In short: I don't need the async loaded information in my unit test, does that mean that I have to initialize my viewmodels differently? – Bruno V Jul 08 '15 at 11:17
  • 1
    Usually for 'unit testing', you'd try to isolate your class under test from external dependencies like the database. If you were using mocked database interactions, then your async task could return straight away. Is using a mocked db context a feasible option? https://msdn.microsoft.com/en-gb/data/dn314429.aspx – forsvarir Jul 08 '15 at 11:32
  • We have considered mocking the database but we have integration tests with an test-database too (that is cleared and refilled when running the tests). We decided to follow the same path for our unit tests because it was already setup and we could save ourselves the hassle of mocking the application. However other issues seem to appear :-). I think it will be more feasible to adjust the viewmodel initialization than to set up mocking. – Bruno V Jul 08 '15 at 11:45

2 Answers2

4

In WPF, this works like a charm.

Sort of. As currently written, the "initialization" task is just ignored. So, there's no error handling, and there's no indication to the UI that the initialization is in progress or completed (i.e., there's no way for it to know when to show a spinner).

In other words, surfacing this information (state as well as the resulting data) would be useful to more than just the unit test code. I have a straightforward data-bindable task wrapper that I wrote a while ago to help in situations like this.

There isn't an alternative way to detect the completion of asynchronous methods if they return a Task; you'd have to expose the Task somehow. I think the best way of exposing it is something like the type I wrote, so that the UI can make use of it as well.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you Stephen, I've applied your task wrapper to the code. This allows me to work async in WPF and I force a wait in my unit tests (unless there is a better way to do it?): `customerOverviewViewModel.CustomerViewModelsLoaded.Task.Wait();`. It definitly solved my thread errors. – Bruno V Jul 09 '15 at 13:49
  • @BrunoV: You can (and should) use `await` for asynchronous unit tests. Just make sure your unit test method returns `Task`, not `void`. I have more info on [`async` unit tests](https://msdn.microsoft.com/en-us/magazine/dn818493.aspx) in an MSDN article. – Stephen Cleary Jul 09 '15 at 14:04
  • the async part is called in the `[TestInitialize]` block. I've found [one of your answers](http://stackoverflow.com/questions/20375130/mstest-async-testinitialize-guarantee-test-fail) about this. However, since I have a lot of unit tests, I searched a solution that could be applied in the `[TestInitialize]` block. So I went for the other 3 point answer which calls Wait() in the initialize. Is there an issue with forcing the async method to run synchronized in unit tests? – Bruno V Jul 09 '15 at 14:20
  • @BrunoV: Not on MSTest; it's just not as efficient there. If you tried to do the same thing on xUnit, you could cause a deadlock. In this case, `TestInitialize` AFAIK does not support `async`, so you'd have to use some hack or another (and I classify `Wait` as a hack :). – Stephen Cleary Jul 09 '15 at 14:37
3

Stephen's answer has all the relevant information. I'd still finish mine to show how you'd actually unit-test an asynchronous ViewModel.

In a nutshell, your model might look like this:

public class CustomerOverviewViewModel : INotifyPropertyChanged 
{
    readonly Task<string> _dataTask;
    readonly Task _notifierTask;

    public event PropertyChangedEventHandler PropertyChanged = delegate {};

    public string Data
    {
        get
        {
            return _dataTask.IsCompleted ? 
                _dataTask.GetAwaiter().GetResult() : 
                "loading...";
        }
    }

    public CustomerOverviewViewModel()
    {
        _dataTask = LoadFullCustomerListAsync();

        Func<Task> notifier = async () =>
        {
            try 
            {           
                await _dataTask;
            }
            finally
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs("Data"));
            }
        };

        // any exception thrown by _dataTask stays dormant in _notifierTask
        // and may go unobserved until GC'ed, but it will be re-thrown 
        // to the whenever CustomerOverviewViewModel.Data is accessed
        // from PropertyChanged event handlers
        _notifierTask = notifier(); 
    }

    async Task<string> LoadFullCustomerListAsync()
    {
        await Task.Delay(1000);
        return "42";
    }
}

Your unit test would now be asynchronous, too:

[TestMethod]
public async Task Test()
{
    var customerOverviewViewModel = new CustomerOverviewViewModel();

    var tcs = new TaskCompletionSource<bool>();

    PropertyChangedEventHandler handler = (s, e) =>
    {
        if (e.PropertyName == "Data")
            tcs.TrySetResult(true);
    };

    customerOverviewViewModel.PropertyChanged += handler;
    try 
    {
        await tcs.Task;
    }
    finally
    {
        customerOverviewViewModel.PropertyChanged -= handler;
    }

    Assert.IsTrue(customerOverviewViewModel.Data == "42");
}
noseratio
  • 59,932
  • 34
  • 208
  • 486