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 11th, 2009 | 7 Comments
7 Responses to “TDD: Helper for checking PropertyChanged event gets raised” Leave your Comment
  1. Beau Crawford says:

    I use the following for testing events:

    bool wasCalled = false;
    viewModel.PropertyChanged += (s, e) => { wasCalled = true; }

    // Invoke action that raises event

    Assert.IsTrue(wasCalled);

  2. Richard says:

    @Beau: I got sick of typing that over and over again :)

  3. Drew Freyling says:

    Are you sure this works? when i run the test it keeps hitting the finalize and never the dispose, therefore never making the assertion.

  4. Richard says:

    Drew,

    It worked on my machine, I promise!! Perhaps the dispose/finalizer needs to be tweaked for your environment/test runner?

  5. Drew Freyling says:

    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”);
    }

  6. Richard says:

    Drew: our app used the WPF MVVM toolkit; view models inherit ViewModelBase which isn’t IDisposable..?

  7. Drew Freyling says:

    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.

Leave a reply

We love to hear your views.