Click here to Skip to main content
6,822,123 members and growing! (17,238 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » General     Intermediate License: The Code Project Open License (CPOL)

WPF: If Carlsberg did MVVM Frameworks Part 5 of n

By Sacha Barber

It would probably be like Cinch a MVVM framework for WPF
C# (C#3.0, C#4.0), .NET (.NET3.5, .NET4.0), WPF, Architect, Dev, Design
Revision:10 (See All)
Posted:7 Aug 2009
Updated:5 Dec 2009
Views:17,363
Bookmarked:34 times
Unedited contribution
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
43 votes for this article.
Popularity: 7.62 Rating: 4.67 out of 5

1

2
10 votes, 23.3%
3
2 votes, 4.7%
4
31 votes, 72.1%
5

Contents

Cinch Article Series Links

  • Cinch primer article
  • A walkthrough of Cinch, and it's internals I
  • A walkthrough of Cinch, and it's internals II
  • How to develop ViewModels using Cinch
  • How to Unit test ViewModels using Cinch, including how to test Background worker threads which may run within Cinch ViewModels
  • A Demo app using Cinch
  • 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

    So I guess the only way to do this is to just start, so lets 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 adresses 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 persmission 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 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 a exposed ICommand that needs testing:

    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;
            }
    
        }
    }
    

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

    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, a 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

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

    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:

    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", 
                ParameterType = typeof(MediatorSendViewModel))]
            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 inteaction?

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

    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:

    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

    Uses an 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 indefinately.

    Here is an example:

    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 article 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:

    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 1st condition as the actual IMessageBoxService implementation would be mocked to provide a single CustomDialogIcons. But what about the next condition how about that. For my (and Cinch the answer lay 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 2 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 2 tests.

    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 is 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:

    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:

    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:

    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:

    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 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 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.

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

    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;
    }
    

    So 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) Commands code (which is of course actually in a ViewModel) :

    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 what the Cancel button (which returns false from the popup) does. Lets also see that:

    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. Lets try and replicate what we did with the actual popup inside a unit test.

    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 DataErrorInfo Supporting Objects

    Remember from part II and part IV we have the idea of either an validating Model/ViewModel using either of the Cinch classes ValidatingObject or ValidatingViewModelBase. These 2 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 type 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. Lets 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 ViewModel that abstracts the Model) that looks like this:

    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:

    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 2 classes are editable thanks to the IEditableObject interface implementation, which gives us the following 3 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. Lets 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 ViewModel that abstracts the Model) that looks like this:

    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:

    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 2nd 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

    History.

    1. xx/xx/09 : Initial release
    2. 05/12/09 : Added in code sample which explains how to add validation rules using the new validation methods of Cinch

     

    Thanks.

    As always votes / comments are welcome.

    License

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

    About the Author

    Sacha Barber


    Member
    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 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

    Occupation: Software Developer (Senior)
    Location: United Kingdom United Kingdom

    Other popular Windows Presentation Foundation articles:

    Article Top
    You must Sign In to use this message board.
    FAQ FAQ 
     
    Noise Tolerance  Layout  Per page   
     Msgs 1 to 22 of 22 (Total in Forum: 22) (Refresh)FirstPrevNext
    GeneralProblems when using MSUnitTest PinmemberMikeEasy8:04 25 Aug '09  
    GeneralRe: Problems when using MSUnitTest PinmvpSacha Barber22:45 25 Aug '09  
    GeneralRe: Problems when using MSUnitTest PinmemberMikeEasy1:46 26 Aug '09  
    GeneralRe: Problems when using MSUnitTest PinmvpSacha Barber3:17 26 Aug '09  
    GeneralAgain PinmemberRaul Mainardi Neto3:47 13 Aug '09  
    GeneralRe: Again PinmvpSacha Barber3:54 13 Aug '09  
    GeneralExcellent Sir Pinmemberarvindjo0:31 12 Aug '09  
    GeneralRe: Excellent Sir PinmvpSacha Barber3:47 12 Aug '09  
    GeneralGreat work PinmemberDaniel Vaughan9:33 11 Aug '09  
    GeneralRe: Great work PinmvpSacha Barber10:24 11 Aug '09  
    QuestionProblem with TabItem? PinmemberMaC_Premium4:04 9 Aug '09  
    AnswerRe: Problem with TabItem? PinmvpSacha Barber9:03 9 Aug '09  
    GeneralRe: Problem with TabItem? PinmemberMaC_Premium21:16 9 Aug '09  
    GeneralRe: Problem with TabItem? PinmvpSacha Barber22:38 9 Aug '09  
    GeneralRe: Problem with TabItem? PinmemberMaC_Premium20:36 10 Aug '09  
    GeneralRe: Problem with TabItem? PinmvpSacha Barber22:47 10 Aug '09  
    AnswerRe: Problem with TabItem? PinmemberMaC_Premium1:48 17 Aug '09  
    GeneralRe: Problem with TabItem? PinmvpSacha Barber2:28 17 Aug '09  
    GeneralExcellent stuff dude. PinmvpPete O'Hanlon12:26 8 Aug '09  
    GeneralRe: Excellent stuff dude. PinmvpSacha Barber1:55 9 Aug '09  
    GeneralGood Job PinmemberDr.Luiji1:27 8 Aug '09  
    GeneralRe: Good Job PinmvpSacha Barber2:00 8 Aug '09  

    General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

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

    PermaLink | Privacy | Terms of Use
    Last Updated: 5 Dec 2009
    Editor:
    Copyright 2009 by Sacha Barber
    Everything else Copyright © CodeProject, 1999-2010
    Web19 | Advertise on the Code Project