![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Intermediate
License: The Code Project Open License (CPOL)
WPF: If Carlsberg did MVVM Frameworks Part 5 of nBy Sacha BarberIt 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
|
||||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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:
The demo app makes use of :
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
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.
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.
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);
}
}
}
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 :
ManualResetEventWhich 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.
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)
}
}
}
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.
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.
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
.....
.....
.....
.....
}
}
}
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
.....
.....
.....
.....
}
}
}
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:
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.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() methodIEditableObject.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);
}
}
}
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:
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);
}
}
}
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.
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);
}
}
}
In the subsequent articles I will be showcasing it roughly like this
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.
As always votes / comments are welcome.
General
News
Question
Answer
Joke
Rant
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 |