If you are doing Rich UI, INotifyPropertyChanged is a pretty important interface. This is as true for WPF as it was for Windows Forms. Consisting solely of an event, it's not any harder to unit test than other events.

You can certainly write each test manually like the following.

[TestMethod]
public void ChangingMyPropertyWillRaiseNotifyEvent_Classic()
{
    // Fixture setup
    bool eventWasRaised = false;
    var sut = new MyClass();
    sut.PropertyChanged += (sender, e) =>
        {
            if (e.PropertyName == "MyProperty")
            {
                eventWasRaised = true;
            }
        };
    // Exercise system
    sut.MyProperty = "Some new value";
    // Verify outcome
    Assert.IsTrue(eventWasRaised, "Event was raised");
    // Teardown
}

Even for a one-off test, this one has a few problems. From an xUnit Test Patterns point of view, there's the issue that the test contains conditional logic, but that aside, the main problem is that if you have a lot of properties, writing all these very similar tests become old hat very soon.

To make testing INotifyPropertyChanged events easier, I created a simple fluent interface that allows me to write the same test like this:

[TestMethod]
public void ChangingMyPropertyWillRaiseNotifyEvent_Fluent()
{
    // Fixture setup
    var sut = new MyClass();
    // Exercise system and verify outcome
    sut.ShouldNotifyOn(s => s.MyProperty)
        .When(s => s.MyProperty = "Some new value");
    // Teardown
}

You simply state for which property you want to verify the event when a certain operation is invoked. This is certainly more concise and intention-revealing than the previous test.

If you have interdependent properties, you can specify than an event was raised when another property was modified.

[TestMethod]
public void ChangingMyPropertyWillRaiseNotifyForDerived()
{
    // Fixture setup
    var sut = new MyClass();
    // Exercise system and verify outcome
    sut.ShouldNotifyOn(s => s.MyDerivedProperty)
        .When(s => s.MyProperty = "Some new value");
    // Teardown
}

The When method takes any Action<T>, so you can also invoke methods, use Closures and what not.

There's also a ShouldNotNotifyOn method to verify that an event was not raised when a particular operation was invoked.

This fluent interface is implemented with an extension method on INotifyPropertyChanged, combined with a custom class that performs the verification. Here are the extension methods:

public static class NotifyPropertyChanged
{
    public static NotifyExpectation<T>
        ShouldNotifyOn<T, TProperty>(this T owner,
        Expression<Func<T, TProperty>> propertyPicker) 
        where T : INotifyPropertyChanged
    {
        return NotifyPropertyChanged.CreateExpectation(owner,
            propertyPicker, true);
    }
 
    public static NotifyExpectation<T> 
        ShouldNotNotifyOn<T, TProperty>(this T owner,
        Expression<Func<T, TProperty>> propertyPicker)
        where T : INotifyPropertyChanged
    {
        return NotifyPropertyChanged.CreateExpectation(owner,
            propertyPicker, false);
    }
 
    private static NotifyExpectation<T>
        CreateExpectation<T, TProperty>(T owner,
        Expression<Func<T, TProperty>> pickProperty,
        bool eventExpected) where T : INotifyPropertyChanged
    {
        string propertyName =
            ((MemberExpression)pickProperty.Body).Member.Name;
        return new NotifyExpectation<T>(owner,
            propertyName, eventExpected);
    }
}

And here's the NotifyExpectation class returned by both extension methods:

public class NotifyExpectation<T>
    where T : INotifyPropertyChanged
{
    private readonly T owner;
    private readonly string propertyName;
    private readonly bool eventExpected;
 
    public NotifyExpectation(T owner,
        string propertyName, bool eventExpected)
    {
        this.owner = owner;
        this.propertyName = propertyName;
        this.eventExpected = eventExpected;
    }
 
    public void When(Action<T> action)
    {
        bool eventWasRaised = false;
        this.owner.PropertyChanged += (sender, e) =>
        {
            if (e.PropertyName == this.propertyName)
            {
                eventWasRaised = true;
            }
        };
        action(this.owner);
 
        Assert.AreEqual<bool>(this.eventExpected,
            eventWasRaised,
            "PropertyChanged on {0}", this.propertyName);
    }
}

You can replace the Assertion with one that matches your test framework of choice (this one was written for MSTest).


Comments

PeteB #
I'm never played with modifying/creating a fluent interface; how can this be extended to check for multiple NotifyProperty events (and NOT events)?

e.g.
sut.ShouldNotifyOn(s => s.MyProperty).AndOn(s => s.MyDependentProperty).AndNotOn(s => s.MyIndependentProperty)
.When(s => s.MyProperty = "Some new value");
2012-08-06 11:39 UTC
Would that add more value than three individual tests?
2012-08-06 11:52 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Thursday, 06 August 2009 17:58:36 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Thursday, 06 August 2009 17:58:36 UTC