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






4.94/5 (46 votes)
It would probably be like Cinch, an MVVM framework for WPF.
Contents
- Introduction
- Prerequisites
- Special Thanks
- Unit Testing
- Testing Commands
- Testing Mediator
- Testing Background Worker Tasks
- Services
- Testing Using Test IMessageBox Service
- Testing Using Test IOpenFileService Service
- Testing Using Test ISaveFileService Service
- Testing Using Test IUIVisualizerService Service
- Testing the Validity of IDataErrorInfo Supporting Objects
- Testing the State of IEditableObject Supporting Objects
- What's Coming Up?
Cinch Article Series Links
- Cinch primer article
- A walkthrough of Cinch, and its internals I
- A walkthrough of Cinch, and its 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:
- Unit Testing
- Testing Commands
- Testing Mediator
- Testing Background Worker Tasks
- Services
- Testing Using Test IMessageBox Service
- Testing Using Test IOpenFileService Service
- Testing Using Test ISaveFileService Service
- Testing Using Test IUIVisualizerService Service
- Testing the Validity of IDataErrorInfo Supporting Objects
- Testing the State of IEditableObject Supporting Objects
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:
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:
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:
We can imagine that we have a 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")]
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:
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
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:
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:
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.Coun
t to be zero in that test.
Here are examples for these two 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 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:
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 show popup windows. When you think about what popup windows would normally be used for, you may begin to understand what
the IUIVisualizerService
s 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 toBeginEdit()
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 supportIEditableObject
, so if you prefer that approach, you would at this point call theBeginEdit()
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 theIEditableObject.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:
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):
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:
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.
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:
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 two classes are editable thanks to the IEditableObject
interface implementation, which gives us the following three methods:
BeginEdit
: Store the current state to backup storeEndEdit
: Apply the new valuesCancelEdit
: 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:
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:
- A demo app using Cinch.
- 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.