Today I am working on my first WPF app, using the WPF Model-View-ViewModel (MVVM) Toolkit. Naturally, we are using TDD — like ASP.NET MVC, WPF ViewModels and ICommands lend themselves very nicely to unit testing, even around difficult dependencies like OpenFileDialog.
Anyway, one problem I am seeing repeated is writing tests for PropertyChanged events firing at the correct time. This is required so that WPF views can display updated values when something changes. For a test helper, I wrote a quick disposable event listener and extension method for this:
[TestMethod]
public void Should_raise_pack_path_property_changed_event()
{
viewModel.AssertRaisesPropertyChangedFor("PackPath");
viewModel.OnFileSelected(@"C:\foo.zip");
}
An assertion will fail if the PropertyChanged event does not fire with the correct property name. Here is the extension method:
public static class INotifyPropertyChangedExtensions
{
public static void AssertRaisesPropertyChangedFor(this INotifyPropertyChanged obj,
string propertyName)
{
new PropertyChangedEventListener(obj, propertyName);
}
}
… and the event listener:
/// <summary>
/// Helper class for asserting a PropertyChanged event gets raised for a particular
/// property. If it hasn't been called by the time this object is disposed, an
/// assertion will fail.</summary>
public class PropertyChangedEventListener : IDisposable
{
bool wasRaised = false;
readonly string expectedPropertyName;
bool IsDisposed = false;
readonly INotifyPropertyChanged obj;
public PropertyChangedEventListener(INotifyPropertyChanged obj, string propertyName)
{
if (obj == null)
throw new ArgumentNullException("obj");
if (propertyName == null)
throw new ArgumentNullException("propertyName");
this.obj = obj;
this.expectedPropertyName = propertyName;
obj.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (this.expectedPropertyName.Equals(e.PropertyName))
this.wasRaised = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool Disposing)
{
if (!IsDisposed)
{
if (Disposing)
{
// Cleanup references...
this.obj.PropertyChanged -= new PropertyChangedEventHandler(OnPropertyChanged);
// Assert we got called
Assert.IsTrue(this.wasRaised,
String.Format("PropertyChanged was not raised for property '{0}'",
this.expectedPropertyName));
}
}
IsDisposed = true;
}
~PropertyChangedEventListener()
{
Dispose(false);
}
}
It’s not fancy, and it’s probably not thread-safe, but it does the trick for our app.
May 11, 2009



7 Comments
Beau Crawford on May 12, 2009 at 2:19 am.
I use the following for testing events:
bool wasCalled = false;
viewModel.PropertyChanged += (s, e) => { wasCalled = true; }
// Invoke action that raises event
Assert.IsTrue(wasCalled);
Richard on May 21, 2009 at 10:58 pm.
@Beau: I got sick of typing that over and over again :)
Drew Freyling on August 20, 2009 at 11:29 am.
Are you sure this works? when i run the test it keeps hitting the finalize and never the dispose, therefore never making the assertion.
Richard on August 26, 2009 at 2:06 pm.
Drew,
It worked on my machine, I promise!! Perhaps the dispose/finalizer needs to be tweaked for your environment/test runner?
Drew Freyling on September 8, 2009 at 8:39 pm.
Ah. The viewmodel must also implement idisposable which would then in turn dispose of the event listener and make the assertion. For example:
using (var viewModel = new ViewModel()) {
viewModel.AssertRaisesPropertyChangedFor(“PackPath”);
viewModel.OnFileSelected(@”C:\foo.zip”);
}
Richard on September 9, 2009 at 12:59 pm.
Drew: our app used the WPF MVVM toolkit; view models inherit ViewModelBase which isn’t IDisposable..?
Drew Freyling on September 25, 2009 at 5:53 pm.
Yeah but you are only making your assertion in the dispose of your event listener. So your test while it will pass, won’t make the assertion unless you dispose of it in your test.