TDD: Helper for checking PropertyChanged event gets raised
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.