Sunday, February 12, 2012

Implementing asynchronous testing

So when I had got my test project set up so it at least ran, I started creating a whole set of tests, most of which just do Assert.Inconclusive("Not yet implemented"). Now I'm going through and implementing them one by one.

Class - ScheduleViewModel contains a private instance of the class Schedule. When I call ScheduleViewModel.Load(), it calls Schedule.Load(), which fires an event on completion and then the method ScheduleViewModel.LoadingComplete() handles that event. Unfortunately,  without just making everything public (or internal, but that's not much cleaner) I can't make my test aware of this event, so that it can wait to assess the results of LoadingComplete()

So what I think I'm doing here, is changing the method ScheduleViewModel.LoadingComplete to catch the event from schedule, and throw another one that will be caught by a new ScheduleViewModel.LoadReallyComplete method. This new event will be visible to the test code, so we can wait on it.

 test method:
            _viewModel = new ScheduleViewModel(pivotView, pivotParam);
            Assert.IsNotNull(_viewModel);
 
             // make sure we wait for the ScheduleLoadingComplete to happen            
            _viewModel.VM_ScheduleLoadingComplete += 
                new EventHandler<ScheduleLoadingEventArgs>(ScheduleCallback);
            _viewModel.LoadSchedule();
            // wait for our method to trigger on the event and set this to true
            EnqueueConditional(() => _callbackDone);
 
            foreach (var slice in _viewModel.EventSlices)
            {
                Assert.IsNotNull(slice);
            }
 
        //event handler helper - triggered by the ScheduleLoading event in the ScheduleViewModel, sets _callbackDone to true
        public void ScheduleCallback(object sender, ScheduleLoadingEventArgs e)
        {
            _callbackDone = true;
        } 


class under test:
  public event EventHandler<ScheduleLoadingEventArgs> VM_ScheduleLoadingComplete;
 
        // this method will be called when the Schedule instance finishes loading. It will then throw its own event
        // that will trigger the LoadPopulatedSchedule() method  
        public void Schedule_ScheduleLoadingComplete(object sender, ScheduleLoadingEventArgs e)
        {
            // throw an event for finished loading
            if (VM_ScheduleLoadingComplete != null)
            {
                VM_ScheduleLoadingComplete(this, e);
            }
        }
 
        public void LoadPopulatedSchedule(object sender, ScheduleLoadingEventArgs e)
        {
//does a lot of stuff            // Fire Event on UI Thread
            view.Dispatcher.BeginInvoke(() =>
            {
                view.OnLoadComplete();
            });
        }



Now that I'd come this far, however, I realised that I actually want to fire my event for the test right at the end of LoadPopulatedSchedule, where I'm currently firing the UI event. So I could get rid of my 'trigger on one event, raise another' method, and add the new event to the end of LoadPopulatedSchedule, right after the UI Dispatcher invoke.(It seems like I should be able to make the test trigger on the UI event, but for now this seems close enough)

Of course, once I had everything triggering correctly on events, it turned out that I actually hadn't initialised the data structure correctly in the first place, and then that I was looking at the wrong set of results, and then that I wasn't correctly clearing my test data so that every subsequent test failed. And once I'd solved all them, I found that the UI method being dispatched was calling a method that referred to variables instantiated in the xaml, so were failing with null references when run under the test harness.


Reading material from this session:
basic stack overflow question
http://blogs.infosupport.com/unit-testing-with-silverlight/
http://www.codebadger.com/blog/post/2009/09/08/Unit-Testing-Silverlight-ViewModels-%28MVVM%29-That-Make-Asynchronous-Calls.aspx

No comments: