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.