Note that there are some explanatory texts on larger screens.

plurals
  1. POHow to test a ViewModel that loads with a BackgroundWorker?
    primarykey
    data
    text
    <p>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:</p> <pre><code>public class MyViewModel { public DelegateCommand LoadDataCommand { get; set; } private List&lt;Data&gt; myData; public List&lt;Data&gt; MyData { get { return myData; } set { myData = value; RaisePropertyChanged(() =&gt; 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); } </code></pre> <p>So that is all pretty simple stuff. However, what I would really like to do is load the data using a <code>BackgroundWorker</code>, and have a 'loading' message displayed on screen. So I change the VM to:</p> <pre><code>private void OnLoadData() { IsBusy = true; // view is bound to IsBusy to show 'loading' message. var bg = new BackgroundWorker(); bg.DoWork += (sender, e) =&gt; { MyData = wcfClient.LoadData(); }; bg.RunWorkerCompleted += (sender, e) =&gt; { IsBusy = false; }; bg.RunWorkerAsync(); } </code></pre> <p>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:</p> <pre><code>[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 &lt; 10; i++) { Thread.Sleep(100); if(vm.MyData != null) return; // success } Assert.Fail("Data not loaded in a reasonable time."); } </code></pre> <p>However that seems really clunky... It works, but just feels dirty. Any better suggestions?</p> <hr> <p><strong>Eventual Solution</strong>:</p> <p>Based on David Hall's answer, to mock a BackgroundWorker, I ended up doing this fairly simple wrapper around <code>BackgroundWorker</code> that defines two classes, one that loads data asynchronously, and one that loads synchronously.</p> <pre><code> 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)); } } } </code></pre> <p>So then in my Unity configuration, I can use SyncWorker for testing, and AsyncWorker for production. My ViewModel then becomes:</p> <pre><code>public class MyViewModel(IWorker bgWorker) { public void OnLoadData() { IsBusy = true; bgWorker.Run( (sender, e) =&gt; { MyData = wcfClient.LoadData(); }, (sender, e) =&gt; { IsBusy = false; }); } } </code></pre> <p>Note that the thing i have marked as <code>wcfClient</code> is actually a Mock in my tests too, so after the call to <code>vm.LoadDataCommand.Execute()</code> I can also validate that <code>wcfClient.LoadData()</code> was called.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload