Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

WPF: If Carlsberg did MVVM Frameworks: Part 5 of n

Rate me:
Please Sign up or sign in to vote.
4.94/5 (56 votes)
7 May 2010CPOL15 min read 130.9K   69   32
It would probably be like Cinch, an MVVM framework for WPF.

Contents

Cinch Article Series Links

Introduction

Last time, we started looking at the what a typical Model and ViewModel might contain when using Cinch.

In this article, I will be looking at the following:

Prerequisites

The demo app makes use of:

  • VS2008 SP1
  • .NET 3.5 SP1
  • SQL Server (see the README.txt in the MVVM.DataAccess project to learn what you have to setup for the demo app database)

Special Thanks

I guess the only way to do this is to just start, so let's get going, shall we? But before we do that, I just need to repeat the special thanks section, with one addition, Paul Stovell, who I forgot to include last time.

Before I start, I would specifically like to say a massive thanks to the following people, without whom this article and the subsequent series of articles would never have been possible. Basically, what I have done with Cinch is studied most of these guys, seen what's hot, what's not, and come up with Cinch, which I hope addresses some new ground not covered in other frameworks.

  • Mark Smith (Julmar Technology), for his excellent MVVM Helper Library, which has helped me enormously. Mark, I know I asked your permission to use some of your code, which you most kindly gave, but I just wanted to say a massive thanks for your cool ideas, some of which I genuinely had not thought of. I take my hat off to you mate.
  • Josh Smith / Marlon Grech (as an atomic pair) for their excellent Mediator implementation. You boys rock, always a pleasure.
  • Karl Shifflett / Jaime Rodriguez (Microsoft boys) for their excellent MVVM Lob tour, which I attended. Well done lads!
  • Bill Kempf, for just being Bill and being a crazy wizard like programmer, who also has a great MVVM framework called Onyx, which I wrote an article about some time ago. Bill always has the answers to tough questions, cheers Bill.
  • Paul Stovell for his excellent delegate validation idea, which Cinch uses for validation of business objects.
  • All of the WPF Disciples, for being the best online group to belong to, IMHO.

Thanks guys/girl, you know who you are.

Unit Testing

The main thrust of this article will be about how to construct unit tests that test as much of your ViewModel/Model code as possible. A wise man once said, show me your Unit tests and if they are good enough, I will be able to understand how the application works.

It is not my job to tell you how much unit testing you should do, that is your choice. But what I can say is that the main idea behind Cinch was to make the ViewModels/Models as testable as humanly possible. I have to say I am actually pretty pleased with how much you can actually test.

The rest of this article will discuss what testing techniques you could use to test your own ViewModel/Model classes, and you can of course examine the attached demo code which has a whole set of NUnit tests, which test all the functionality of the demo app.

Testing Commands

One of the smallest but complete testable units within a ViewModel will be a ICommand that is exposed to the UI. The way I like to set up my unit tests is to mainly test the exposed ICommand properties that the View binds to, as these are the things that actually cause something to happen in the ViewModel that exposes these ICommand properties.

Using the ICommand interface implementation, SimpleCommand within Cinch could not be easier, as it even exposes a CommandSucceeded property that the Unit test can examine after the exposed ICommand has been run.

So suppose you have a ViewModel that is setup like this, and has an exposed ICommand that needs testing:

C#
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

using Cinch;
using MVVM.Models;

namespace MVVM.ViewModels
{
    public class MainWindowViewModel : Cinch.ViewModelBase
    {
        private SimpleCommand doSomethingCommand;

        public MainWindowViewModel()
        {
            //Create DoSomething command
            doSomethingCommand = new SimpleCommand
            {
                CanExecuteDelegate = x => CanExecuteDoSomethingCommand,
                ExecuteDelegate = x => ExecuteAddCustomerCommand()
            };
        }

        public SimpleCommand DoSomethingCommand 
        {
            get { return doSomethingCommand; }
        }

        private Boolean CanExecuteDoSomethingCommand
        {
            get
            {
                return true;
            }
        }

        private void ExecuteDoSomethingCommand()
        {
            DoSomethingCommand.CommandSucceeded = false;
            
        //DO SOMETHING
        //DO SOMETHING
            DoSomethingCommand.CommandSucceeded = true;
        }
    }
}

The easiest way to test this exposed ICommand would be to do something like the following, within a Unit test:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void MainWindowViewModel_DoSomethingCommand_Test()
        {
            MainWindowViewModel mainWindowVM = new MainWindowViewModel();
           
            mainWindowVM.DoSomethingCommand.Execute(null);
            Assert.AreEqual(mainWindowVM.DoSomethingCommand.CommandSucceeded, true);
        }
    }
}

You can see above, it is just a matter of obtaining the correct ICommand, and then executing it, and seeing if the CommandSucceeded property is set to true. If you do not like relying on a simple Boolean flag, an ICommand usually does something, such as Delete an Item or Add an Item, so you could check for that resultant state instead, which would validate that the ICommand ran successfully; it is up to you.

Notice that the Unit test is very granular, it only tests a single exposed ICommand, which is something I would actually recommend.

Testing Mediator

If you recall from part II, there is a message sending/receiving mechanism that is provided by the ViewModelBase class within Cinch, this is called the Mediator. Just to refresh you, here is an image of how it works:

Image 1

We can imagine that we have a chunk of ViewModel code that sends a message via the Mediator, such as:

C#
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
using System.Windows.Data;

using Cinch;
using MVVM.Models;
using MVVM.DataAccess;

namespace MVVM.ViewModels
{
    /// <summary>
    /// Summy ViewModel that send messages using Mediator
    /// </summary>
    public class MediatorSendViewModel : Cinch.ViewModelBase
    {
        #region Ctor
        public MediatorSendViewModel()
        {
            //send a Message using Mediator
            Mediator.NotifyColleagues<MediatorSendViewModel>(
                "MediatorSendViewModelCreated",
                this);
        }
        #endregion
    }
}

And we have some ViewModel code that is interested in receiving this message such as:

C#
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
using System.Windows.Data;

using Cinch;
using MVVM.Models;
using MVVM.DataAccess;

namespace MVVM.ViewModels
{
    /// <summary>
    /// Summy ViewModel that receives messages using Mediator
    /// </summary>
    public class MediatorReceiveViewModel : Cinch.ViewModelBase
    {
        #region Data
        private List<MediatorSendViewModel> itemsCreated = 
                new List<MediatorSendViewModel>();
        #endregion

        #region Ctor
        public MediatorReceiveViewModel()
        {

        }
        #endregion

        #region Mediator Message Sinks
        [MediatorMessageSink("MediatorSendViewModelCreated")]
        private void MediatorSendViewModelCreatedMessageSink(
            MediatorSendViewModel theNewObject)
        {
            itemsCreated.Add(theNewObject);
        }
        #endregion

        #region Public Properties
        static PropertyChangedEventArgs itemsCreatedChangeArgs =
             ObservableHelper.CreateArgs<MediatorReceiveViewModel>(x => x.ItemsCreated);

        public List<MediatorSendViewModel> ItemsCreated
        {
            get { return itemsCreated; }
            set
            {
                if (itemsCreated == null)
                {
                    itemsCreated = value;
                    NotifyPropertyChanged(itemsCreatedChangeArgs);
                }
            }
        }
        #endregion
    }
}

So how do we test this interaction?

Well, it is fairly easy. These interactions are, at the end of the day, just classes, so we just check the results of the message being received to see that the message payload did what it was meant to do. Here is an example Unit test that tests the above Send/Receive:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.DataAccess;
using MVVM.ViewModels;
using MVVM.Models;
using Cinch;
using System.Threading;

namespace MVVM.Test
{
    /// <summary>
    /// Tests the Mediator functionality
    /// </summary>
    [TestFixture]
    public class Mediator_Tests
    {
        [Test]
        public void TestMediator()
        {
            MediatorReceiveViewModel receiver = new MediatorReceiveViewModel();
            Assert.AreEqual(receiver.ItemsCreated.Count, 0);

            MediatorSendViewModel sender = new MediatorSendViewModel();
            Assert.AreEqual(receiver.ItemsCreated.Count, 1);
        }
    }
}

Testing Background Worker Tasks

Whilst threading is hard, I have tried to ensure that there is at least one testable background worker threading option in Cinch. This comes in the form of the BackgroundTaskManager<T> class that has been discussed at length in the previous articles.

I am not going to go into the internals of the BackgroundTaskManager<T> class, as this has been covered in previous articles. For now, let us suppose we have a Cinch ViewModel, which has a background task set up (ICommand that calls the LazyFetchOrdersForCustomer() method left out for brevity) which lazy loads some orders for a selected customer, which looks like this:

C#
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
using System.Windows.Data;

using Cinch;
using MVVM.Models;
using MVVM.DataAccess;

namespace MVVM.ViewModels
{

    public class AddEditCustomerViewModel : Cinch.WorkspaceViewModel
    {
        private BackgroundTaskManager<DispatcherNotifiedObservableCollection<OrderModel>> 
            bgWorker = null;

        private SimpleCommand doSomethingCommand;

        public AddEditCustomerViewModel()
        {
            //Create DoSomething command
            doSomethingCommand = new SimpleCommand
            {
                CanExecuteDelegate = x => CanExecuteDoSomethingCommand,
                ExecuteDelegate = x => ExecuteAddCustomerCommand()
            };
            //setup background worker
            SetUpBackgroundWorker();
        }

        public SimpleCommand DoSomethingCommand 
        {
            get { return doSomethingCommand; }
        }
     
        static PropertyChangedEventArgs bgWorkerChangeArgs =
            ObservableHelper.CreateArgs<AddEditCustomerViewModel>(x => x.BgWorker);

        public BackgroundTaskManager<DispatcherNotifiedObservableCollection<OrderModel>> BgWorker
        {
            get { return bgWorker; }
            set
            {
                bgWorker = value;
                NotifyPropertyChanged(bgWorkerChangeArgs);
            }
        }
        
        private void SetUpBackgroundWorker()
        {
            bgWorker = new BackgroundTaskManager<DispatcherNotifiedObservableCollection<OrderModel>>(
                () =>
                {
                    //Do Background Work
                },
                (result) =>
                {

                    //Use background worker results
                    
                    //Store Count
                    this.Count = result.Count;
                });
        }

        /// <summary>
        /// Fetches all Orders for customer
        /// </summary>
        private void LazyFetchOrdersForCustomer()
        {
            if (CurrentCustomer != null &&
                CurrentCustomer.CustomerId.DataValue > 0)
            {
                bgWorker.RunBackgroundTask();
            }
        }
        
        private Boolean CanExecuteDoSomethingCommand
        {
            get
            {
                return true;
            }
        }

        private void ExecuteDoSomethingCommand()
        {
            DoSomethingCommand.CommandSucceeded = false;
            LazyFetchOrdersForCustomer();
            DoSomethingCommand.CommandSucceeded = true;
        }
    }
}

How do you think we could Unit test this? We are effectively doing something in the background that we expect to complete, but we do not know how long it will take. Yet, we need our Unit test to play nice and wait either for a sensible time limit or until told that the background task has completed.

Whilst this sounds hard, the System.Threading namespace has several classes that can help us here, and the actual BackgroundTaskManager<T> class also has a mechanism that can help us out.

I will not be covering the System.Threading namespace classes in depth, but you should most definitely look up:

  • ManualResetEvent

Which is a WaitHandle object used for Threading synchronization. 

So marching on, let us see what Cinch allows you to do, in terms of Unit testing and background threading.

Use the Completed Event in Conjunction With a WaitHandle

Use a ManualResetEvent WaitHandle which is set by the BackgroundTaskManager<T> completed event to signal the WaitHandle to state that the background task is done.

This is a nice option, as the reader of the test can see a Completed event handler for the BackgroundTaskManager<T> class, so the unit test may just be that little bit more legible, which when dealing with threading is always helpful. There is also an optional timeout that can be specified with an exit condition for the WaitHandle inside the Unit test, which means we do not wait indefinitely.

Here is an example:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void AddEditCustomerViewModel_BgWorker_Test()
        {
            Int32 EXPECTED_VALUE =100;

            AddEditCustomerViewModel addEditCustomerVM = 
                                     new AddEditCustomerViewModel();
            
            //Set up WaitHandle
            ManualResetEvent manualResetEvent = new ManualResetEvent(false);
            
            //Wait for the completed event, and then signal the WaitHandle that 
            //the test can continue
            addEditCustomerVM .BgWorker.BackgroundTaskCompleted += 
                              delegate(object sender, EventArgs args)
            {
               // Signal the waiting NUnit thread that we're ready to move on.
               manualEvent.Set();
            };
                        
            //Do the work (via the ICommand)
            addEditCustomerVM.DoSomethingCommand.Execute(null);
            
            //Wait for reasonable time (5 seconds, with exit
            //state set to "not signalled" on WaitHandle)
            manualResetEvent.WaitOne(5000, false);
            
            //Assert : Check if background task results
            //and expected results are the same
            Assert.AreEqual(EXPECTED_VALUE, addEditCustomerVM.Count)
        }
    }
}

Services

Remember from part III, that we covered services and how we had not only a WPF service implementation but also a Unit test version. The following sub sections will show you how to use the unit test services implementations to correctly test Cinch Model/ViewModels.

Note: Do not forget, the Unit test service implementations are Dependency Injected into the Unity IOC container using the settings within the App.Config of the current test application. You can see an example of this in the attached demo app.

Testing Using Test IMessageBox Service

As stated in several of the previous articles in the Cinch series, what makes Cinch different to using Mocks for the services is that Cinch is able to fully cover any and all ViewModel code by the use of a Queue of callback delegates (Func<T,TResult>) which are set up in the unit tests. So for example, imagine we had a chunk of code; link this in a ViewModel that required testing:

C#
var messager = this.Resolve<IMessageBoxService>();

if (messager.ShowYesNo("Clear all items?", CustomDialogIcons.Question)
    == CustomDialogResults.Yes)
{
    if (messager.ShowYesNo("This procedure can not be undone, Proceed", 
        CustomDialogIcons.Question)  == CustomDialogResults.Yes)

        stuff.Clear();
}

Using a combination of the Cinch provided Unit Test IMessageBoxService implementation, and Unit dependency injection (discussed in previous Cinch articles), we are able to test this piece of code in ways that just would not be possible with Mocks.

For example, using Mocks, we would certainly be able to satisfy the first condition as the actual IMessageBoxService implementation would be mocked to provide a single CustomDialogIcons. But what about the next condition? How about that? The answer lies in callback delegates that you set up in your unit tests. By using this approach, it is 100% possible to drive the ViewModel code down any path you want and achieve total code coverage.

To get total code coverage, I would have two tests: one that supplied a CustomDialogResults.Yes, then a CustomDialogResults.No, so I would expect the stuff.Count not to be zero in that test.

I would then have another test where the Unit test provides a CustomDialogResults.Yes then another CustomDialogResults.Yes, so I would expect the stuff.Count to be zero in that test.

Here are examples for these two tests:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void ShouldNotClearStuff_Tests()
        {
            //Setup some hyperthetical ViewModel
            SomeViewModel x = new SomeViewModel();

            //Resolve service to get Test service implementation
            TestMessageBoxService testMessageBoxService =
                (TestMessageBoxService)
                    ViewModelBase.ServiceProvider.Resolve<IMessageBoxService>();

            //Queue up the responses we expect for our given TestMessageBoxService
            //for a given ICommand/Method call within the test ViewModel

            testMessageBoxService.ShowYesNoResponders.Enqueue
                (() =>
                    {
    
                        return CustomDialogResults.Yes;
                    }
                );

           testMessageBoxService.ShowYesNoResponders.Enqueue
                (() =>
                    {
    
                        return CustomDialogResults.No;
                    }
                );

            Int32 oldStuffCount = x.Stuff.Count;

            //Execute some hyperthetical command
            x.SomeCommand.Execute(null);

            //Make sure clear did not work, as the ViewModel should not have cleared the
            //Stuff list, unless it saw Yes -> Yes
            Assert.AreEqual(x.Stuff.Count, oldStuffCount );
        }

        [Test]
        public void ShouldClearStuff_Tests()
        {
            //Setup some hyperthetical ViewModel
            SomeViewModel x = new SomeViewModel();

            //Resolve service to get Test service implementation
            TestMessageBoxService testMessageBoxService =
                (TestMessageBoxService)
                    ViewModelBase.ServiceProvider.Resolve<IMessageBoxService>();
    
            //Queue up the responses we expect for our given TestMessageBoxService
            //for a given ICommand/Method call within the test ViewModel

            testMessageBoxService.ShowYesNoResponders.Enqueue
                (() =>
                    {
    
                        return CustomDialogResults.Yes;
                    }
                );

           testMessageBoxService.ShowYesNoResponders.Enqueue
                (() =>
                    {
    
                        return CustomDialogResults.Yes;
                    }
                );

            Int32 oldStuffCount = x.Stuff.Count;

               //Execute some hyperthetical command
               x.SomeCommand.Execute(null);

            //Make sure clear worked, as the ViewModel should have cleared the
            //Stuff list, as it saw Yes -> Yes
           Assert.AreNotEqual(x.Stuff.Count, oldStuffCount );
           Assert.AreEqual(x.Stuff.Count, 0);
        }
    }
}

If you understand how this works, the next couple of services will be fairly obvious too, as they all follow the same pattern.

Testing Using Test IOpenFileService Service

The IOpenFileService works very similarly to the IMessageBoxService, with the exception that it is required to deal with a filename and a bool? result from the dialog (of course, there is no dialog in the Unit test implementation, it works largely as just demonstrated).

So for some bit of code like this in a ViewModel:

C#
var ofd = this.Resolve<IOpenFileService>();

bool? result = ofd.ShowDialog(null);

if (result.HasValue && result)
{
    File.Open(ofd.FileName);
    ....
    ....
}

We could deal with this in a test and supply a valid file name to the ViewModel (just like the user picked an actual file) as follows:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void OpenSomeFile_Tests()
        {
            //Setup some hyperthetical ViewModel
            SomeViewModel x = new SomeViewModel();

            //Resolve service to get Test service implementation
            TestOpenFileService testOpenFileService =
                (TestOpenFileService)
                    ViewModelBase.ServiceProvider.Resolve<IOpenFileService>();
    
            //Queue up the responses we expect for our given TestOpenFileService 
            //for a given ICommand/Method call within the test ViewModel
            testMessageBoxService.ShowDialogResponders.Enqueue
                (() =>
                    {
                      testOpenFileService.FileName = @"c:\test.txt";
                      return true
                    }
                );

            //Do some testing based on the File requested
            .....
            .....
            .....
            .....
        }
    }
}

Testing Using Test ISaveFileService Service

The ISaveFileService works very similarly to the IOpenFileService.

So for some bit of code like this in a ViewModel:

C#
var sfd = this.Resolve<ISaveFileService>();

bool? result = ofd.ShowDialog(null);

if (result.HasValue && result)
{
    File.Create(sfd.FileName);
    ....
    ....
}

We could deal with this in a test and supply a valid file name to the ViewModel (just like the user picked an actual file location to save to) as follows:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void OpenSomeFile_Tests()
        {
            //Setup some hyperthetical ViewModel
            SomeViewModel x = new SomeViewModel();

            //Resolve service to get Test service implementation
            TestSaveFileService testSaveFileService =
                (TestSaveFileService)
                    ViewModelBase.ServiceProvider.Resolve<ISaveFileService>();
    
            //Queue up the responses we expect for our given TestSaveFileService 
            //for a given ICommand/Method call within the test ViewModel

            testMessageBoxService.ShowDialogResponders.Enqueue
                (() =>
                    {
                        string path = @"c:\test.txt";
                        testSaveFileService.FileName = path ;
                        return true;
                    }
                );

            //Do some testing based on the File save location the Unit test set up
            .....
            .....
            .....
            .....
        }
    }
}

Testing Using Test IUIVisualizerService Service

Remember from part III and part IV, we have a service which can be used to show popup windows. When you think about what popup windows would normally be used for, you may begin to understand what the IUIVisualizerServices job actually is.

You see, typically a popup window will be opened and given some object (typically a Model/ViewModel which has just stored its current state, say using the IEditableObject support which we saw in part II and part IV) which represents the current state, and then the popup will alter the current state object, and either pass back a true (DialogResult.Ok) or a false (user cancelled the popup operation), at which point the object that launched the popup must decide what to do with its own state/the state of the object it passed to the popup window.

The way the demo app deals with this is as follows:

  • The ViewModel has a Model(s) which supports IEditableObject, so just before the popup is shown, and Model(s) are called to BeginEdit() which stores the current state in an internal storage. Remember, I prefer to use a Model, but last time in part IV, I also stated that Cinch allows ViewModels to support IEditableObject, so if you prefer that approach, you would at this point call the BeginEdit() for the current ViewModel.
  • Next, the popup window is shown using the IUIVisualizerServices, passing in some state (this is typically a ViewModel), which we have a backup for anyhow, thanks to the state snapshot we take just before showing the popup, using the IEditableObject.BeginEdit() method.
  • The user works with the popup, changing values etc., which are affecting the current popup state, which is either a Model you passed in, or a ViewModel if that is what you prefer, and then the user either presses:
    • OK: so just close the popup, the state passed to the popup has been altered by actions on the popup which the users are happy with, and wish to accept.
    • Cancel: so close the popup, and rollback the state of the object you passed to the popup using the IEditableObject.CancelEdit() method. And you are back to a state before you even showed the popup.

Imagine I had some ViewModel code that looked like this to show the popup:

C#
private void ExecuteEditOrderCommand()
{
    EditOrderCommand.CommandSucceeded = false;
    addEditOrderVM.CurrentViewMode = ViewMode.EditMode;            
    CurrentCustomerOrder.BeginEdit();
    addEditOrderVM.CurrentCustomer = CurrentCustomer;
    bool? result = uiVisualizerService.ShowDialog("AddEditOrderPopup", 
                                                  addEditOrderVM);

    if (result.HasValue && result.Value)
    {
        CloseActivePopUpCommand.Execute(true);
    }
    EditOrderCommand.CommandSucceeded = true;
}

From the explanation above, it is now obvious that all that is happening here is we are taking a snapshot of the Customer.Order state and then showing the popup. And just for completeness, here is the popup windows Save button (DialogResult.Ok true) Command's code (which is of course actually in a ViewModel):

C#
private void ExecuteSaveOrderCommand()
{
    try
    {
        SaveOrderCommand.CommandSucceeded = false;

        if (!CurrentCustomerOrder.IsValid)
        {
            messageBoxService.ShowError("There order is invalid");
            SaveOrderCommand.CommandSucceeded = false;
            return;
        }

        //Order is valid, so end the edit and try and save/update it
        this.CurrentCustomerOrder.EndEdit();

        switch (currentViewMode)
        {
            #region AddMode
            ......
            ......
            ......
            ......
            #endregion
            #region EditMode
            //EditMode
            case ViewMode.EditMode:
                Boolean orderUpdated =
                    DataService.UpdateOrder(
                        TranslateUIOrderToDataLayerOrder(CurrentCustomerOrder));

                if (orderUpdated)
                {
                    messageBoxService.ShowInformation(
                        "Sucessfully updated order");
                    this.CurrentViewMode = ViewMode.ViewOnlyMode;
                }
                else
                {
                    messageBoxService.ShowError(
                        "There was a problem updating the order");
                }
                SaveOrderCommand.CommandSucceeded = true;
                //Use the Mediator to send a Message to AddEditCustomerViewModel to tell it a new
                //or editable Order needs actioning
                Mediator.NotifyColleagues<Boolean>("AddedOrderSuccessfullyMessage", true);
                break;
            #endregion
        }
    }
    catch (Exception ex)
    {
        Logger.Log(LogType.Error, ex);
        messageBoxService.ShowError(
            "There was a problem saving the order");
    }
}

It can be seen that this actually saves (removed for clarity) or edits an Order object. So what about the Cancel button (which returns false from the popup)? Let's also see that:

C#
private void ExecuteCancelOrderCommand()
{
    CancelOrderCommand.CommandSucceeded = false;
    switch (CurrentViewMode)
    {
        case ViewMode.EditMode:
            this.CurrentCustomerOrder.CancelEdit();
            CloseActivePopUpCommand.Execute(false);
            CancelOrderCommand.CommandSucceeded = true;
            break;
        default:
            this.CurrentCustomerOrder.CancelEdit();
            CancelOrderCommand.CommandSucceeded = true;
            break;
    }
}

Sorry for going slightly off topic there, but I thought it was a necessary divergence, just so you understand the ViewModel code and Unit test code we are about to go through.

So how do we go about doing the same thing as the actual popup inside a Unit test? Well, it actually turns out to be pretty easy. Let's try and replicate what we did with the actual popup inside a unit test.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{

    [TestFixture]
    public class Tests
    {
        [Test]
        public void OrderModel_SaveState_Tests()
        {
            SomeViewModel vm = new SomeViewModel();

            //get old value
            Int32 oldQuantity = vm.CurrentCustomerOrder.Quantity.DataValue;
            //uses Datarapper

            //get test service
            TestUIVisualizerService testUIVisualizerService =
              (TestUIVisualizerService)
                ViewModelBase.ServiceProvider.Resolve<IUIVisualizerService>();

            //Queue up the response we expect for our given TestUIVisualizerService
            //for a given ICommand/Method call within the test ViewModel
            testUIVisualizerService.ShowDialogResultResponders.Enqueue
            (() =>
               {
                //simulate here any state changes you would have altered in the popup
                  //basically change the state of anything you want
                  vm.CurrentCustomerOrder.Quantity.DataValue = 56;  //uses Datarapper
                  vm.CurrentCustomerOrder.ProductId.DataValue = 2;  //uses Datarapper
            
                  return true;
               }
            );

            //Execute the command to show the popup
            vm.ExecuteEditOrderCommand.Execute(null);

            //see if state has changed
            Assert.AreNotEqual(oldQuantity, vm.CurrentCustomerOrder.Quantity.DataValue);
        }

        [Test]
        public void OrderModel_CancelEdit_Tests()
        {
            SomeViewModel vm = new SomeViewModel();

            //get old value
            Int32 oldQuantity = vm.CurrentCustomerOrder.Quantity.DataValue;
            //uses Datarapper

            //get test service
            TestUIVisualizerService testUIVisualizerService =
              (TestUIVisualizerService)
                ViewModelBase.ServiceProvider.Resolve<IUIVisualizerService>();
            
            //Queue up the response we expect for our given TestUIVisualizerService
            //for a given ICommand/Method call within the test ViewModel
            testUIVisualizerService.ShowDialogResultResponders.Enqueue
            (() =>
                {
                   //Simulate user prressing cancel, so not alter some state, 
                   //but return like user just pressed cancel
                   vm.CurrentCustomerOrder.Quantity.DataValue = 56;  //uses Datarapper
                          return false;
                }
            );

             //Execute the command to show the popup
            vm.ExecuteEditOrderCommand.Execute(null);

             //see if state has changed
            Assert.AreEqual(oldQuantity, vm.CurrentCustomerOrder.Quantity.DataValue);
         }
    }
}

Testing the Validity of IDataErrorInfo Supporting Objects

Remember from part II and part IV, we have the idea of either a validating Model/ViewModel using either of the Cinch classes ValidatingObject or ValidatingViewModelBase. These two classes are validatable thanks to the IDataErrorInfo interface implementation. Cinch works with the IDataErrorInfo interface by supplying validation rules inside the object being validated. An example of this is shown below.

Cinch supports the following types of rules:

  • SimpleRule: A delegate based rule, where all validation is done in a callback delegate
  • RegxRulee: A Regular Expression rule for validating text based data

So how can we go about testing the validity of a given IDataErrorInfo supporting object?

Well, it is fairly easy actually. Let's suppose we have a model class (again, this could be a ValidatingViewModelBase if you are not in control of your own Models, or prefer to have a ViewModel that abstracts the Model) that looks like this:

C#
using System;

using Cinch;
using MVVM.DataAccess;
using System.ComponentModel;

namespace MVVM.Models
{
    public class OrderModel : Cinch.EditableValidatingObject
    {
        private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> customerId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> quantity = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> productId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<DateTime> deliveryDate = new DataWrapper<DateTime>();
        private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;

        //rules
        private static SimpleRule quantityRule;

        public OrderModel()
        {
            #region Create DataWrappers

            OrderId = new DataWrapper<Int32>(this, orderIdChangeArgs);
            CustomerId = new DataWrapper<Int32>(this, customerIdChangeArgs);
            ProductId = new DataWrapper<Int32>(this, productIdChangeArgs);
            Quantity = new DataWrapper<Int32>(this, quantityChangeArgs);
            DeliveryDate = new DataWrapper<DateTime>(this, deliveryDateChangeArgs);

            //fetch list of all DataWrappers, so they can be used again later without the
            //need for reflection
            cachedListOfDataWrappers =
                DataWrapperHelper.GetWrapperProperties<OrderModel>(this);

            #endregion

            #region Create Validation Rules

            quantity.AddRule(quantityRule);

            #endregion    
        }
        
        static OrderModel()
        {
            quantityRule = new SimpleRule("DataValue", "Quantity can not be < 0",
                      (Object domainObject)=>
                      {
                          DataWrapper<Int32> obj = (DataWrapper<Int32>)domainObject;
                          return obj.DataValue <= 0;
                      });
        }
        
        /// <summary>
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects IsValid state into
        /// a combined IsValid state for the whole Model
        /// </summary>
        public override bool IsValid
        {
            get
            {
                //return base.IsValid and use DataWrapperHelper, if you are
                //using DataWrappers
                return base.IsValid &&
                    DataWrapperHelper.AllValid(cachedListOfDataWrappers);
            }
        }
        .....
        .....
        .....
        .....
        .....
    }
}

All we would then need to do to test the validity of such an object from a Unit test would be something like the following:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void OrderModel_InValid_State_Tests()
        {
        
            OrderModel model = new OrderModel();
            model.Quantity = new DataWrapper 
            {
              DataValue = 0,
              IsEditable = true
            };
        
            //check to see if object is invalid 
            //thanks to rules we supplied in the object
            Assert.AreEqual(model.IsValid, false);
        }

        [Test]
        public void OrderModel_Valid_State_Tests()
        {
            OrderModel model = new OrderModel();
            model.Quantity = new DataWrapper 
                {
                  DataValue = 10,
                  IsEditable = true
                };

            //check to see if object is valid 
            //thanks to rules we supplied in the object
            Assert.AreEqual(model.IsValid, true);

        }
    }
}

Testing the State of IEditableObject Supporting Objects

Remember from part II and part IV, we have the idea of either an editable Model/ViewModel using either of the Cinch classes EditableValidatingObject or EditableValidatingViewModelBase. These two classes are editable thanks to the IEditableObject interface implementation, which gives us the following three methods:

  • BeginEdit: Store the current state to backup store
  • EndEdit: Apply the new values
  • CancelEdit: Retrieve the old values and overwrite the current values, effectively cancelling the edit

So how can we go about testing the state of a given IEditableObject supporting object?

Well, it is fairly easy actually. Let's suppose we have a model class (again, this could be a  EditableValidatingViewModelBase if you are not in control of your own Models, or prefer to have a ViewModel that abstracts the Model) that looks like this:

C#
using System;

using Cinch;
using MVVM.DataAccess;
using System.ComponentModel;

namespace MVVM.Models
{
    public class OrderModel : Cinch.EditableValidatingObject
    {
        private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> customerId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> quantity = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> productId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<DateTime> deliveryDate = new DataWrapper<DateTime>();
        private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;

        public OrderModel()
        {
            ....
            ....
            ....
            ....
        }
        ....
        ....
        ....
        ....

        /// <summary>
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects into the BeginEdit state
        /// </summary>
        protected override void OnBeginEdit()
        {
            base.OnBeginEdit();
            //Now walk the list of properties in the CustomerModel
            //and call BeginEdit() on all Cinch.DataWrapper<T>s.
            //we can use the Cinch.DataWrapperHelper class for this
            DataWrapperHelper.SetBeginEdit(cachedListOfDataWrappers);
        }

        /// <summary>
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects into the EndEdit state
        /// </summary>
        protected override void OnEndEdit()
        {
            base.OnEndEdit();
            //Now walk the list of properties in the CustomerModel
            //and call CancelEdit() on all Cinch.DataWrapper<T>s.
            //we can use the Cinch.DataWrapperHelper class for this
            DataWrapperHelper.SetEndEdit(cachedListOfDataWrappers);
        }

        /// <summary>
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects into the CancelEdit state
        /// </summary>
        protected override void OnCancelEdit()
        {
            base.OnCancelEdit();
            //Now walk the list of properties in the CustomerModel
            //and call CancelEdit() on all Cinch.DataWrapper<T>s.
            //we can use the Cinch.DataWrapperHelper class for this
            DataWrapperHelper.SetCancelEdit(cachedListOfDataWrappers);
        }
        #endregion
    }
}

All we would then need to do to test the editability of such an object from a Unit test would be something like the following:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

using MVVM.ViewModels;
using Cinch;

namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void OrderModel_EditThenCancelEdit_Tests()
        {
            OrderModel model = new OrderModel();
            model.Quantity = new DataWrapper 
            {
              DataValue = 10,
              IsEditable = true
            };
        
            //check just assigned value        
            Assert.AreEqualmodel.Quantity.DataValue, 10);

            //Begin an Edit
            model.BeginEdit();

            //change some value
            model.Quantity = new DataWrapper 
            {
              DataValue = 22,
              IsEditable = true
            };            

            // Cancel the Edit
            model.CancelEdit(); 

            //Ensure new values are rolled back
            //to old values after a CancelEdit()
            Assert.AreEqual(model.Quantity.DataValue, 10);
        }

        [Test]
        public void OrderModel_EditThenEndEdit_Tests()
        {
            OrderModel model = new OrderModel();
            model.Quantity = new DataWrapper 
            {
              DataValue = 10,
              IsEditable = true
            };

            //check just assigned value
            Assert.AreEqual(model.Quantity.DataValue, 10);

            //Begin an Edit
            model.BeginEdit();

            //change some value
            model.Quantity = new DataWrapper 
            {
              DataValue = 22,
              IsEditable = true
            };            

            //End the Edit
            model.EndEdit(); 

            //Ensure new values is now the same as EndEdit() accepted value
            Assert.AreEqual(model.Quantity.DataValue, 22);
        }
    }
}

What's Coming Up?

In the subsequent articles, I will be showcasing it roughly like this:

  1. A demo app using Cinch.
  2. A code generator for developing quick Cinch ViewModels/Models, and maybe more if I find some more time. The code generator will be written in Cinch so the code generator will also serve as a second example of how to use Cinch in your own projects.

That's It, Hope You Liked It

That is actually all I wanted to say right now, but I hope from this article, you can see where Cinch is going and how it could help you with MVVM. As we continue our journey, we will see how to develop an app with Cinch.

Thanks

As always, votes / comments are welcome.

History

  • xx/xx/09: Initial release.
  • 05/12/09: Added a code sample which explains how to add validation rules using the new validation methods of Cinch.
  • 07/05/10: Updated MediatorMessageSink attribute usage.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
SuggestionWhy can't we... Pin
Adam Meyer19-Feb-13 11:32
Adam Meyer19-Feb-13 11:32 
GeneralRe: Why can't we... Pin
Sacha Barber19-Feb-13 19:55
Sacha Barber19-Feb-13 19:55 
QuestionMy vote of 5 Pin
Filip D'haene19-Jun-11 4:16
Filip D'haene19-Jun-11 4:16 
AnswerRe: My vote of 5 Pin
Sacha Barber21-Jun-11 2:24
Sacha Barber21-Jun-11 2:24 
QuestionHow do you use RegexRule Pin
Sevententh11-May-10 0:21
Sevententh11-May-10 0:21 
AnswerRe: How do you use RegexRule Pin
Sacha Barber11-May-10 0:24
Sacha Barber11-May-10 0:24 
GeneralTesting, testing, 1...2...3... Pin
Josh Smith26-Apr-10 13:01
Josh Smith26-Apr-10 13:01 
GeneralRe: Testing, testing, 1...2...3... Pin
Sacha Barber26-Apr-10 19:56
Sacha Barber26-Apr-10 19:56 
GeneralTypo Pin
Leung Yat Chun24-Apr-10 21:45
Leung Yat Chun24-Apr-10 21:45 
GeneralRe: Typo Pin
Sacha Barber24-Apr-10 22:31
Sacha Barber24-Apr-10 22:31 
GeneralProblems when using MSUnitTest Pin
MikeEasy25-Aug-09 7:04
MikeEasy25-Aug-09 7:04 
GeneralRe: Problems when using MSUnitTest Pin
Sacha Barber25-Aug-09 21:45
Sacha Barber25-Aug-09 21:45 
GeneralRe: Problems when using MSUnitTest Pin
MikeEasy26-Aug-09 0:46
MikeEasy26-Aug-09 0:46 
GeneralRe: Problems when using MSUnitTest Pin
Sacha Barber26-Aug-09 2:17
Sacha Barber26-Aug-09 2:17 
GeneralAgain Pin
Raul Mainardi Neto13-Aug-09 2:47
Raul Mainardi Neto13-Aug-09 2:47 
Once more, the force is with you Master Barber...

Have another 5
GeneralRe: Again Pin
Sacha Barber13-Aug-09 2:54
Sacha Barber13-Aug-09 2:54 
GeneralExcellent Sir Pin
arvindjo11-Aug-09 23:31
arvindjo11-Aug-09 23:31 
GeneralRe: Excellent Sir Pin
Sacha Barber12-Aug-09 2:47
Sacha Barber12-Aug-09 2:47 
GeneralGreat work Pin
Daniel Vaughan11-Aug-09 8:33
Daniel Vaughan11-Aug-09 8:33 
GeneralRe: Great work Pin
Sacha Barber11-Aug-09 9:24
Sacha Barber11-Aug-09 9:24 
QuestionProblem with TabItem? Pin
MaC_Premium9-Aug-09 3:04
MaC_Premium9-Aug-09 3:04 
AnswerRe: Problem with TabItem? Pin
Sacha Barber9-Aug-09 8:03
Sacha Barber9-Aug-09 8:03 
GeneralRe: Problem with TabItem? Pin
MaC_Premium9-Aug-09 20:16
MaC_Premium9-Aug-09 20:16 
GeneralRe: Problem with TabItem? Pin
Sacha Barber9-Aug-09 21:38
Sacha Barber9-Aug-09 21:38 
GeneralRe: Problem with TabItem? Pin
MaC_Premium10-Aug-09 19:36
MaC_Premium10-Aug-09 19:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.