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:
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()
{
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;
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
{
public class MediatorSendViewModel : Cinch.ViewModelBase
{
#region Ctor
public MediatorSendViewModel()
{
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
{
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
{
[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()
{
doSomethingCommand = new SimpleCommand
{
CanExecuteDelegate = x => CanExecuteDoSomethingCommand,
ExecuteDelegate = x => ExecuteAddCustomerCommand()
};
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>>(
() =>
{
},
(result) =>
{
this.Count = result.Count;
});
}
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:
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();
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
addEditCustomerVM .BgWorker.BackgroundTaskCompleted +=
delegate(object sender, EventArgs args)
{
manualEvent.Set();
};
addEditCustomerVM.DoSomethingCommand.Execute(null);
manualResetEvent.WaitOne(5000, false);
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()
{
SomeViewModel x = new SomeViewModel();
TestMessageBoxService testMessageBoxService =
(TestMessageBoxService)
ViewModelBase.ServiceProvider.Resolve<IMessageBoxService>();
testMessageBoxService.ShowYesNoResponders.Enqueue
(() =>
{
return CustomDialogResults.Yes;
}
);
testMessageBoxService.ShowYesNoResponders.Enqueue
(() =>
{
return CustomDialogResults.No;
}
);
Int32 oldStuffCount = x.Stuff.Count;
x.SomeCommand.Execute(null);
Assert.AreEqual(x.Stuff.Count, oldStuffCount );
}
[Test]
public void ShouldClearStuff_Tests()
{
SomeViewModel x = new SomeViewModel();
TestMessageBoxService testMessageBoxService =
(TestMessageBoxService)
ViewModelBase.ServiceProvider.Resolve<IMessageBoxService>();
testMessageBoxService.ShowYesNoResponders.Enqueue
(() =>
{
return CustomDialogResults.Yes;
}
);
testMessageBoxService.ShowYesNoResponders.Enqueue
(() =>
{
return CustomDialogResults.Yes;
}
);
Int32 oldStuffCount = x.Stuff.Count;
x.SomeCommand.Execute(null);
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()
{
SomeViewModel x = new SomeViewModel();
TestOpenFileService testOpenFileService =
(TestOpenFileService)
ViewModelBase.ServiceProvider.Resolve<IOpenFileService>();
testMessageBoxService.ShowDialogResponders.Enqueue
(() =>
{
testOpenFileService.FileName = @"c:\test.txt";
return true
}
);
.....
.....
.....
.....
}
}
}
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()
{
SomeViewModel x = new SomeViewModel();
TestSaveFileService testSaveFileService =
(TestSaveFileService)
ViewModelBase.ServiceProvider.Resolve<ISaveFileService>();
testMessageBoxService.ShowDialogResponders.Enqueue
(() =>
{
string path = @"c:\test.txt";
testSaveFileService.FileName = path ;
return true;
}
);
.....
.....
.....
.....
}
}
}
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 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:
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;
}
this.CurrentCustomerOrder.EndEdit();
switch (currentViewMode)
{
#region AddMode
......
......
......
......
#endregion
#region 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;
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();
Int32 oldQuantity = vm.CurrentCustomerOrder.Quantity.DataValue;
TestUIVisualizerService testUIVisualizerService =
(TestUIVisualizerService)
ViewModelBase.ServiceProvider.Resolve<IUIVisualizerService>();
testUIVisualizerService.ShowDialogResultResponders.Enqueue
(() =>
{
vm.CurrentCustomerOrder.Quantity.DataValue = 56; vm.CurrentCustomerOrder.ProductId.DataValue = 2;
return true;
}
);
vm.ExecuteEditOrderCommand.Execute(null);
Assert.AreNotEqual(oldQuantity, vm.CurrentCustomerOrder.Quantity.DataValue);
}
[Test]
public void OrderModel_CancelEdit_Tests()
{
SomeViewModel vm = new SomeViewModel();
Int32 oldQuantity = vm.CurrentCustomerOrder.Quantity.DataValue;
TestUIVisualizerService testUIVisualizerService =
(TestUIVisualizerService)
ViewModelBase.ServiceProvider.Resolve<IUIVisualizerService>();
testUIVisualizerService.ShowDialogResultResponders.Enqueue
(() =>
{
vm.CurrentCustomerOrder.Quantity.DataValue = 56; return false;
}
);
vm.ExecuteEditOrderCommand.Execute(null);
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;
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);
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;
});
}
public override bool IsValid
{
get
{
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
};
Assert.AreEqual(model.IsValid, false);
}
[Test]
public void OrderModel_Valid_State_Tests()
{
OrderModel model = new OrderModel();
model.Quantity = new DataWrapper
{
DataValue = 10,
IsEditable = true
};
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:
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()
{
....
....
....
....
}
....
....
....
....
protected override void OnBeginEdit()
{
base.OnBeginEdit();
DataWrapperHelper.SetBeginEdit(cachedListOfDataWrappers);
}
protected override void OnEndEdit()
{
base.OnEndEdit();
DataWrapperHelper.SetEndEdit(cachedListOfDataWrappers);
}
protected override void OnCancelEdit()
{
base.OnCancelEdit();
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
};
Assert.AreEqualmodel.Quantity.DataValue, 10);
model.BeginEdit();
model.Quantity = new DataWrapper
{
DataValue = 22,
IsEditable = true
};
model.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
};
Assert.AreEqual(model.Quantity.DataValue, 10);
model.BeginEdit();
model.Quantity = new DataWrapper
{
DataValue = 22,
IsEditable = true
};
model.EndEdit();
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.