If you’re coding your Silverlight or WPF app and you’re using the ViewModel Pattern, you’ve probably heard of the INotifyPropertyChanged interface. INotifyPropertyChange is the glue that allows changes to values in your ViewModel classes to bubble up to your data-bound xaml user interfaces.
When a change happens in a ViewModel property, the property raises INotifyPropertyChanged’s PropertyChanged event. If this event doesn’t get thrown, then the user interface won’t know about the change and will continue to show the old value on the screen.
It’s pretty important. Sounds like it should be unit tested, huh?
The Problem: Well, I was writing some unit tests for INotifyPropertyChanged in one of my ViewModel classes over the weekend. The way I was doing it was really cumbersome.
[TestMethod]
public void ViewModelField_NotifyPropertyChanged_NotifiesOnNewValue()
{
string originalValue = "original value";var instance = new ViewModelField
(originalValue); Assert.IsInstanceOfType(instance, typeof(INotifyPropertyChanged));
bool gotEvent = false;
((INotifyPropertyChanged)instance).PropertyChanged +=
delegate(object sender, PropertyChangedEventArgs e)
{
gotEvent = true;
Assert.AreEqual("Value", e.PropertyName, "PropertyName was wrong.");
};string newValue = "new value";
instance.Value = newValue;Assert.AreEqual
(newValue, instance.Value, "Value didn't change."); Assert.IsTrue(gotEvent, "Didn't get the PropertyChanged event.");
}
In the code above, I’m using a delegate to subscribe to the PropertyChanged event and then setting the gotEvent variable to true if I get the event and then verifying that the PropertyName value on the event is what I expect. So I set up the delegate and then modify the property. When that’s done, I check that gotEvent is now set to true.
It works but it’s kind of ugly. If it’s ugly when I look at just one unit test method for INotifyPropertyChanged, image what it looks like when I have lots of test methods. Yah. Really ugly and there’s a lot of largely duplicated code. It definitely violates the DRY Principle.
The Solution: I needed something clean and reusable so I created a tester utility class called NotifyPropertyChangedTester.
NotifyPropertyChangedTester encapsulates all the logic of subscribing to INotifyPropertyChanged PropertyChanged event and hanging on to the events so that I can “Assert” on them.
[TestMethod]
public void ViewModelField_NotifyPropertyChanged_NotifiesOnNewValue()
{
var viewModelInstance = new ViewModelField("original value"); var tester = new NotifyPropertyChangedTester(viewModelInstance);
string newValue = "new value";
// change the value
viewModelInstance.Value = newValue;// check the property value
Assert.AreEqual(newValue, viewModelInstance.Value, "Value didn't change."); // check if INotifyPropertyChanged worked as expected
Assert.AreEqual(1, tester.Changes.Count, "Change count was wrong.");
tester.AssertChange(0, "Value");
}
The code above is that same unit test refactored to use the NotifyPropertyChangedTester class. The delegate() logic is gone and NotifyPropertyChangedTester has a utility method called AssertChange() that contains the assertions for checking if the PropertyChanged event was right.
My $0.02, I think this is a lot cleaner but it might not be obviously cleaner until you need to do a slightly more complex test that needs to check more than just one INotifyPropertyChanged event at a time like the code block below.
[TestMethod]
public void MemberEditorViewModel_NotifyPropertyChanged_GetEventsWhenValuesChange()
{
var viewModel = new MemberEditorViewModel(
MockRepositoryInstance.Stub()); Assert.AreEqual
(0, viewModel.Id, "Id");
Assert.AreEqual(String.Empty, viewModel.FirstName, "FirstName");
Assert.AreEqual(String.Empty, viewModel.LastName, "LastName");
Assert.AreEqual(String.Empty, viewModel.EmailAddress, "EmailAddress");
Assert.AreEqual(false, viewModel.ReceiveEmails, "ReceiveEmails"); NotifyPropertyChangedTester tester = new NotifyPropertyChangedTester(viewModel);
Assert.AreEqual
(0, tester.Changes.Count, "Changes count was wrong."); viewModel.Id = 1234;
viewModel.FirstName = "fn";
viewModel.LastName = "ln";
viewModel.ReceiveEmails = true;
viewModel.EmailAddress = "email";Assert.AreEqual
(5, tester.Changes.Count, "Changes count was wrong."); tester.AssertChange(0, "Id");
tester.AssertChange(1, "FirstName");
tester.AssertChange(2, "LastName");
tester.AssertChange(3, "ReceiveEmails");
tester.AssertChange(4, "EmailAddress");
}
The implementation for NotifyPropertyChangedTester is not too complex. Here it is.
public class NotifyPropertyChangedTester
{
public NotifyPropertyChangedTester(INotifyPropertyChanged viewModel)
{
if (viewModel == null)
{
throw new ArgumentNullException("viewModel", "Argument cannot be null.");
}this.Changes = new List
(); viewModel.PropertyChanged += new PropertyChangedEventHandler(viewModel_PropertyChanged);
}void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Changes.Add(e.PropertyName);
}public List
Changes { get; private set; } public void AssertChange(int changeIndex, string expectedPropertyName)
{
Assert.IsNotNull(Changes, "Changes collection was null.");Assert.IsTrue(changeIndex < Changes.Count,
"Changes collection contains '{0}' items and does not have an element at index '{1}'.",
Changes.Count,
changeIndex);Assert.AreEqual
(expectedPropertyName,
Changes[changeIndex],
"Change at index '{0}' is '{1}' and is not equal to '{2}'.",
changeIndex,
Changes[changeIndex],
expectedPropertyName);
}
}
-Ben