views:

291

answers:

4

In MVVM it is normal to connect View to the ViewModel with data binding.

Therefore if the name of a properties changes on one of the Model objects that is databound to there is no compiler error.

When the compiler will not stop a bug, the next thing I think of is “UnitTest”, However

How do you unit test this without spending forever writing a GUI test?

Is there a system that will check that all the properties that are bound to is valid, (without having to run the UI) that I can call in a unit test?

I am looking for something that will take the view, and then loop over all WPF controls, for each WPF control it will look at all the binding and check if they are valid.


By the way there have been a few good questions about how to make OnPropertyChanged safe, and/or how to test it (But done of these get down to the level of a WPF view.)


I have put a bounty on this question, as someone must have thought hard about the problem and come up with soltions.

+2  A: 

Really good question. Voted it up. I would like to know the answer too.

One of the best practices I know (suggested by Josh Smith, thanks Gishu for pointing to this) is having base view model class to check in the OnPropertyChanged() method whether property really exists. E.g.:

abstract class ViewModelBase
{
    [Conditional("DEBUG")]
    public void VerifyPropertyName(string propertyName)
    {
        // Verify that the property name matches a real,  
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            if (this.ThrowOnInvalidPropertyName)
                throw new ArgumentException(propertyName);

            string msg = "Invalid property name: " + propertyName;
            Debug.Fail(msg);
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        VerifyPropertyName(propertyName);

        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

But this wouldn't help you to find spelling problems in XAML. Hmm... I don't know any existing solution for this. Maybe guys from WPF Disciples could suggest something. I think I would go with PresentationTraceSources.DataBindingSource and add to his Listners collection instance of TextWriterTraceListener and then monitor output. As soon as we get an error or warning on our radar we should fail a test.

Found good example: WPF Snippet - Detecting Binding Errors

Hope this helps. At least a bit :).

Cheers, Anvaka.

Anvaka
I think I saw this approach - first in Josh Smith's WPF Article on TreeViews. You might plug the source here (for attribution). He recommended making a Base VM Class that has this behavior of verifying the data-bound property names.
Gishu
Thank you, Gishu. Found it in MSDN Magazine. Added the reference :). Cheers.
Anvaka
I have added a few links to the question on testing INotifyPropertyChanged
Ian Ringrose
A: 

As Anvaka points out, using a base class for your view model that checks property names can help avoid this particular problem (though it won't tell you when your VM class does its own property-change notification and ignores the method in the base class, not that I've ever seen anything like that happen in my code).

And you can (and should) instrument your code so that things that aren't working fail in a way that's visible to you. The thing that's kind of paradoxical about this is that if you know what things may fail and you watch them, they won't, because the fact that you're watching them will keep you from making the mistakes that lead them to fail (like writing a template selector that doesn't always return a template).

But fundamentally, the view is the UI, so I would be pretty surprised to find methods of testing it that weren't also methods for testing the UI.

Robert Rossney
+1  A: 

I know that this is not the direct answer to your question.

If you know the name of the control element, that you expect to be bound against you can do something like the test below (using nunit). This is the rough version. But here you use expressions and explicitly test that the property is in a binding

 [Test]
    public void TestBindings()
    {
        TestBinding<IndividualSolutionViewModel, string>(x => x.Name, "Name", TextBlock.TextProperty);
    }

    private void TestBinding<TViewModel,TResult>(Expression<Func<TViewModel, TResult>> property, string elementName, 
        DependencyProperty dependencyProperty)
    {
        string memberName = ExpressionHelper.GetPropertyName(property); // f.ex v => v.Name will return Name
        TestBinding(memberName, elementName, dependencyProperty);

    }

    private void TestBinding(string memberName, string elementInControlName, DependencyProperty dependencyProperty)
    {
        //object viewModel = new IndividualSolutionViewModel();
        var view = new IndividualSolutionView();

        //Assert.That(view.DataContext, Is.EqualTo(viewModel));

        var element = view.FindName(elementInControlName);
        Assert.That(element, Is.Not.Null, string.Format("Unable to find the element {0} in view {1}", elementInControlName, view.Name));
        Assert.That(element, Is.InstanceOf(typeof(DependencyObject)));

        var binding = BindingOperations.GetBinding(element as DependencyObject, dependencyProperty);
        Assert.That(binding, Is.Not.Null, string.Format("Could not find a binding for the control {0}", elementInControlName));

        Assert.That(binding.Path.Path, Is.EqualTo(memberName));
    }

Ps. You have to add this to the app.config

<configSections>
    <sectionGroup name="NUnit">
        <section type="System.Configuration.NameValueSectionHandler"
                 name="TestRunner"></section>
    </sectionGroup>
</configSections>
<NUnit>
    <TestRunner>
        <add value="STA" key="ApartmentState"></add>
    </TestRunner>
</NUnit>
Jeppe Vammen Kristensen
would it be possible to loop over all WPF controls in the view and then loop over all dependencyProperties on each control from a unit test?
Ian Ringrose
I think it is possible. This link has a lot of information about how to traverse the visual tree (and return enumerables of DependencyObjects) http://www.scottlogic.co.uk/blog/colin/2010/03/linq-to-visual-tree/. I think it's possible somehow to check all there dependency properties to see if they have an expected. But I haven't the magic solution. In my example you need the expected dependency property that has the Binding.
Jeppe Vammen Kristensen
+1  A: 

I think I've come up with something that may work using simple reflection, and adapting some code I've used in the past (the code for the FindBindingsRecursively method was written by Martin Bennedik's as part of his Enterprise WPF Validation Control):

[TestMethod]
public void CheckWpfBindingsAreValid()
{
    // instansiate the xaml view and set DataContext
    var yourView = new YourView(); 
    yourView.DataContext = YourViewModel;

    FindBindingsRecursively(yourView,
            delegate(FrameworkElement element, Binding binding, DependencyProperty dp)
            {
                var type = yourView.DataContext.GetType();

                // check that each part of binding valid via reflection
                foreach (string prop in binding.Path.Path.Split('.'))
                {
                    PropertyInfo info = type.GetProperty(prop);
                    Assert.IsNotNull(info);
                    type = info.PropertyType;
                }
    });
}

private delegate void FoundBindingCallbackDelegate(FrameworkElement element, Binding binding, DependencyProperty dp);

private void FindBindingsRecursively(DependencyObject element, FoundBindingCallbackDelegate callbackDelegate)
{
    // See if we should display the errors on this element
    MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Static |
                BindingFlags.Public |
                BindingFlags.FlattenHierarchy);

    foreach (MemberInfo member in members)
    {
        DependencyProperty dp = null;

        // Check to see if the field or property we were given is a dependency property
        if (member.MemberType == MemberTypes.Field)
        {
            FieldInfo field = (FieldInfo)member;
            if (typeof(DependencyProperty).IsAssignableFrom(field.FieldType))
            {
                dp = (DependencyProperty)field.GetValue(element);
            }
        }
        else if (member.MemberType == MemberTypes.Property)
        {
            PropertyInfo prop = (PropertyInfo)member;
            if (typeof(DependencyProperty).IsAssignableFrom(prop.PropertyType))
            {
                dp = (DependencyProperty)prop.GetValue(element, null);
            }
        }

        if (dp != null)
        {
            // Awesome, we have a dependency property. does it have a binding? If yes, is it bound to the property we're interested in?
            Binding bb = BindingOperations.GetBinding(element, dp);
            if (bb != null)
            {
                // This element has a DependencyProperty that we know of that is bound to the property we're interested in. 
                // Now we just tell the callback and the caller will handle it.
                if (element is FrameworkElement)
                {
                    callbackDelegate((FrameworkElement)element, bb, dp);
                }
            }
        }
    }

    // Now, recurse through any child elements
    if (element is FrameworkElement || element is FrameworkContentElement)
    {
        foreach (object childElement in LogicalTreeHelper.GetChildren(element))
        {
            if (childElement is DependencyObject)
            {
                FindBindingsRecursively((DependencyObject)childElement, callbackDelegate);
            }
        }
    }
}
Bermo
the link to Enterprise WPF Validation Control seems to be broken
Ian Ringrose
I've updated the link - hopefully it works now for you.
Bermo