Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Testing Bindings In WPF

I was inspired to get off my duff and blog today by a tweet from Scott Hanselman. I didn’t really dig into the context of his tweet, but it was about TDD and WPF. In case you haven’t figured it out, I’m a big fan of WPF and TDD. Luckily, I’ve had the opportunity to work on a number of varied WPF projects (most using TDD) over the last couple of years. One problem I experienced over and over was incorrect bindings in my Xaml (as I alluded to in this post). Binding errors are silent. They sneak up on you ninja-like. If you are keeping an eye on your Output window you can catch them, but it is very easy for them to just slip by.

Karate LessonsAbout two years ago, Rob and I were working on a student records application for the FSU College of Music. The system handled the entire lifecycle of a student, from application to graduation, and that involved lots of data. We had a number of screens in the app that were merely large data entry screens. Binding errors where a problem, and it seemed like a new batch showed up every time we made a release to the users.

The errors might have been the result of renaming a bound property, bad spelling, or some other such silliness. They were easy mistakes to make and thus I began to think that we really needed a way to automatically test them.

Well, I’m lazy and time passed (maybe a year) and then I mentioned it again to Rob. The next day he committed a prototype to the Caliburn trunk. (He’s like my own personal Ayende.)

Here’s an example test copied directly from Caliburn’s documentation:

public class TestBase
{
    static TestBase()
    {
        var app = new MyApplication();
        app.InitializeComponent();
    }
}
[TestFixture]
public class TestCase : TestBase
{
    [Test]
    public void Test()
    {
        var validator = Validator.For<MyUserControl, MyModel>();
        var result = validator.Validate();

        Assert.That(result.HasErrors, Is.False, result.ErrorSummary);
    }
}

In TestBase, we initialize the actual application so that we have access to application level resources. We need to this in a static constructor because we don’t want more than one instance of the application in the AppDomain.

Now, check out the Test() method. The static class Validator.For<T,K>() method returns an instance of BindingValidator<K>. The type T is some FrameworkElement that contains the bindings we’re interested in and K is the type that the FrameworkElement is bound to. Calling the Validate() method causes Caliburn to instantiate T, crawl it’s tree, and collect all the bindings. It then compares those bindings to the type K to see if they are valid. Validate() actually returns an instance of ValidationResult<K>, which is a summary of everything that Caliburn found while comparing the bindings against the actual type T.

The assertion in the test means that we expect Caliburn to find no errors, and if it does to provide us the summary. This is useful because there might be a number of binding errors and the ErrorSummary will give us the whole list.

At this point, I’m just repeat what’s stated in Caliburn’s documentation. NH Prof was the first project where we’ve actually used the Testability features of Caliburn. Let’s check out a real live test from it:

[TestFixture]
public class MessageBoxViewTextFixture : ViewTestFixtureBase
{
    private readonly ValidationResult<MessagePresenter> bindings;

    public MessageBoxViewTextFixture()
    {
        bindings = Validator.For<MessageBoxView, MessagePresenter>()
            .Validate();
    }

    [Test]
    public void BindingsDoNotHaveErrors()
    {
        Assert.That(bindings.HasErrors, Is.False, bindings.ErrorSummary);
    }

    [Test]
    public void MessageIsBound()
    {
        Assert.That(bindings.WasBoundTo(x => x.Message));
    }
}

This test is for the view that displays a simple popup message. We have a simple presenter that backs the view and it has a message to display in its property Message. It’s tested independent of the view. (This is nice because we have a case in NH Prof where a single presenter might be bound to multiple views.)

The ViewTestFixtureBase is identical to TestBase from the example above. Since it’s expensive to have Caliburn instantiate and crawl the UI validating the bindings, we only want to do this once. (I’m not really sure why I used the constructor, instead of [TestFixtureSetUp], but I did.)

The first test covers 90% of what you typically need, catching spelling errors, etc. The second test however explicitly checks to see if we are bound to the Message property on the presenter. This is useful because that’s the core reason for this view. There may be other properties bound, but this is the one we definitely care about. (I think this affords liberty to designers while ensuring that a view delivers the business value that was intended.)

Now, let’s pretend that I have a bad binding in my markup. Here’s the core snippet for the view used in the test above, MessageBoxView:

<Grid MaxWidth="260">
    <StackPanel>
        <TextBlock Foreground="{StaticResource Text}" 
                   FontSize="14"
                   TextWrapping="Wrap"
                   Text="{Binding Messyage}" />
        <Button Content="OK"
                Margin="4 8 4 4"
                HorizontalAlignment="Center"
                IsCancel="True" />
    </StackPanel>    
</Grid>

Oops, I misspelled Message in the binding! When I execute my tests, they both fail. The first because there is a binding error and the second because it can’t find Message. Here’s the actual exception from the first test:

NUnit.Framework.AssertionException:   [MessageBoxView.Grid.StackPanel.TextBlock] The property 'Messyage' was not found on 'MessagePresenter'.
  Expected: False
  But was:  True

Notice that it actually gives you the path to the offending binding: MessageBoxView.Grid.StackPanel.TextBlock. Now that’s just cool! Thanks Rob!

I can’t explain how much pain it saves to be able to do this. If you are doing TDD with WPF, you really need to check this out.

I’ll post over the weekend some tests that are a bit more BDD in their styles.


Posted 02-19-2009 10:56 PM by Christopher Bennage
Filed under: , , , , ,

[Advertisement]

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)