13

One of the nice things about MVVM is the testability of the ViewModel. In my particular case, I have a VM that loads some data when a command is called, and its corresponding test:

public class MyViewModel
{
    public DelegateCommand LoadDataCommand { get; set; }

    private List<Data> myData;
    public List<Data> MyData
    {
        get { return myData; }
        set { myData = value; RaisePropertyChanged(() => MyData); }
    }

    public MyViewModel()
    {
        LoadDataCommand = new DelegateCommand(OnLoadData);
    }

    private void OnLoadData()
    {
        // loads data over wcf or db or whatever. doesn't matter from where...
        MyData = wcfClient.LoadData();
    }
}

[TestMethod]
public void LoadDataTest()
{
    var vm = new MyViewModel();
    vm.LoadDataCommand.Execute();
    Assert.IsNotNull(vm.MyData);
}

So that is all pretty simple stuff. However, what I would really like to do is load the data using a BackgroundWorker, and have a 'loading' message displayed on screen. So I change the VM to:

private void OnLoadData()
{
    IsBusy = true; // view is bound to IsBusy to show 'loading' message.

    var bg = new BackgroundWorker();
    bg.DoWork += (sender, e) =>
    {
      MyData = wcfClient.LoadData();
    };
    bg.RunWorkerCompleted += (sender, e) =>
    {
      IsBusy = false;
    };
    bg.RunWorkerAsync();
}

This works fine visually at runtime, however my test now fails because of the property not being loaded immediately. Can anyone suggest a good way to test this kind of loading? I suppose what I need is something like:

[TestMethod]
public void LoadDataTest()
{
    var vm = new MyViewModel();
    vm.LoadDataCommand.Execute();

    // wait a while and see if the data gets loaded.
    for(int i = 0; i < 10; i++)
    {
        Thread.Sleep(100);
        if(vm.MyData != null)
            return; // success
    }
    Assert.Fail("Data not loaded in a reasonable time.");
}

However that seems really clunky... It works, but just feels dirty. Any better suggestions?


Eventual Solution:

Based on David Hall's answer, to mock a BackgroundWorker, I ended up doing this fairly simple wrapper around BackgroundWorker that defines two classes, one that loads data asynchronously, and one that loads synchronously.

  public interface IWorker
  {
    void Run(DoWorkEventHandler doWork);
    void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete);
  }

  public class AsyncWorker : IWorker
  {
    public void Run(DoWorkEventHandler doWork)
    {
      Run(doWork, null);
    }

    public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
    {
      var bg = new BackgroundWorker();
      bg.DoWork += doWork;
      if(onComplete != null)
        bg.RunWorkerCompleted += onComplete;
      bg.RunWorkerAsync();
    }
  }

  public class SyncWorker : IWorker
  {
    public void Run(DoWorkEventHandler doWork)
    {
      Run(doWork, null);
    }

    public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
    {
      Exception error = null;
      var args = new DoWorkEventArgs(null);
      try
      {
        doWork(this, args);
      }
      catch (Exception ex)
      {
        error = ex;
        throw;
      }
      finally
      {
        onComplete(this, new RunWorkerCompletedEventArgs(args.Result, error, args.Cancel));
      }
    }
  }

So then in my Unity configuration, I can use SyncWorker for testing, and AsyncWorker for production. My ViewModel then becomes:

public class MyViewModel(IWorker bgWorker)
{
    public void OnLoadData()
    {
        IsBusy = true;
        bgWorker.Run(
          (sender, e) =>
          {
            MyData = wcfClient.LoadData();
          },
          (sender, e) =>
          {
            IsBusy = false;
          });
    }
}

Note that the thing i have marked as wcfClient is actually a Mock in my tests too, so after the call to vm.LoadDataCommand.Execute() I can also validate that wcfClient.LoadData() was called.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
CodingWithSpike
  • 42,906
  • 18
  • 101
  • 138
  • adding a CoROutine supports as in Caliburn could help too. – Felice Pollano Jun 23 '11 at 14:00
  • "defines two classes, one that loads data asynchronously, and one that loads synchronously." Wow, that pattern sounds [like something I've seen before...](http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.aspx) –  Jun 23 '11 at 16:11
  • @Will - Funny you mention that. After posting my edit, I found part of the Prism guide that talks about using the `SynchronizationContext` but the way it was written up only 1/2 made sense to me, and didn't really mention testing. I'll have to find a good example of using it and see if I can substitute that in instead. – CodingWithSpike Jun 23 '11 at 17:26
  • Actually, the Prism guide also mentions using WPF's `Dispatcher`, as also mentioned here: http://stackoverflow.com/questions/1949789/using-synchronizationcontext-for-sending-events-back-to-the-ui-for-winforms-or-wp but again that seems less testable. – CodingWithSpike Jun 23 '11 at 17:32
  • Your eventual solution helped me greatly. Thank you. – Rick Glos Jul 24 '12 at 11:27

2 Answers2

13

Introduce a mock/fake background worker which verifies that you call it correctly but returns immediately with a canned response.

Change your view model to allow for injection of dependencies, either through property injection or constructor injection (I'm showing constructor injection below) and then when testing you pass in the fake background worker. In the real world you inject the real implementation when creating the VM.

public class MyViewModel
{
    private IBackgroundWorker _bgworker;

    public MyViewModel(IBackgroundWorker bgworker)
    {
        _bgworker = bgworker;
    }

    private void OnLoadData()    
    {        
        IsBusy = true; // view is bound to IsBusy to show 'loading' message.        

        _bgworker.DoWork += (sender, e) =>        
        {          
            MyData = wcfClient.LoadData();        
        };        
        _bgworker.RunWorkerCompleted += (sender, e) =>        
        {          
            IsBusy = false;        
        };        
        _bgworker.RunWorkerAsync();    
    }

}

Depending on your framework (Unity/Prism in your case) wiring up the correct background worker should not be too hard to do.

The one problem with this approach is that most Microsoft classes including BackGroundWorker don't implement interfaces so faking/mocking them can be tricky.

The best approach I've found is to create your own interface for the object to mock and then a wrapper object that sits on top of the actual Microsoft class. Not ideal since you have a thin layer of untested code, but at least that means the untested surface of your app moves into testing frameworks and away from application code.

David Hall
  • 32,624
  • 10
  • 90
  • 127
  • That is a pretty good idea. We are using Prism and Unity, so I could wire up Unity to inject mock BgWorkers for testing. Interesting suggestion. – CodingWithSpike Jun 23 '11 at 13:56
  • @rally25rs it is the "standard" way of doing this for certain values of standard :) I'm adding a bit more discussion on the concept. – David Hall Jun 23 '11 at 13:59
  • Here is a interface/inherited background worker http://stackoverflow.com/questions/848179/designing-an-interface-for-backgroundworker – David Hall Jun 23 '11 at 14:05
  • +1 - This answers a question that I didn't realize I urgently needed answered. – Robert Rossney Jun 23 '11 at 14:20
  • 1
    "The one problem with this approach is that most Microsoft classes including BackGroundWorker don't implement interfaces so faking/mocking them can be tricky." - Yep, I think that is the reason I didn't think of this in the first place. I didn't see a good way to mock it. If I have to wrapper the whole thing, I might just make some kind of `BGWorkerFactory` to inject, instead of a single BGWorker, since it is probably possible for a VM to be loading more than 1 data element at a time. If I come up with a working factory / wrapper, I'll append it to my question. – CodingWithSpike Jun 23 '11 at 14:44
0

You can avoid the extra abstraction if you are willing to trade it for a small amount of view model pollution (i.e. introducing code that is only used for the sake of your tests) as follows:

First add an optional AutoResetEvent (or ManualResetEvent) to your view model constructor and make sure that you "set" this AutoResetEvent instance when your background worker finishes the "RunWorkerCompleted" handler.

public class MyViewModel {   
  private readonly BackgroundWorker _bgWorker;
  private readonly AutoResetEvent _bgWorkerWaitHandle;

  public MyViewModel(AutoResetEvent bgWorkerWaitHandle = null) {
    _bgWorkerWaitHandle = bgWorkerWaitHandle;

    _bgWorker = new BackgroundWorker();
    _bgWorker.DoWork += (sender, e) => {          
      //Do your work
    };        
    _bgworker.RunWorkerCompleted += (sender, e) => {          
      //Configure view model with results

      if (_bgWorkerWaitHandle != null) {
         _bgWorkerWaitHandle.Set();
      }
    };
    _bgWorker.RunWorkerAsync();
  }
}

Now you can pass in an instance as part of your unit test.

[Test]
public void Can_Create_View_Model() {
  var bgWorkerWaitHandle = new AutoResetEvent(false); //Make sure it starts off non-signaled
  var viewModel = new MyViewModel(bgWorkerWaitHandle);
  var didReceiveSignal = bgWorkerWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
  Assert.IsTrue(didReceiveSignal, "The test timed out waiting for the background worker to complete.");
  //Any other test assertions
}

This is precisely what the AutoResetEvent (and ManualResetEvent) classes were designed for. So aside from the slight view model code pollution, I think this solution is quite neat.

Brian Hinchey
  • 3,601
  • 1
  • 39
  • 36