在使用WPF的MVVM中,我如何对ViewModel和View之间的链接进行单元测试

Ian*_*ose 20 wpf model-view-controller unit-testing mvvm

在MVVM中,通过数据绑定将View连接到ViewModel是很正常的.

因此,如果属性的名称在其中一个模型对象上更改为数据绑定到没有编译器错误.

当编译器不会阻止错误时,我想到的下一件事是"UnitTest",但是

如何在不花费永远编写GUI测试的情况下对其进行单元测试?

是否有一个系统会检查所有绑定的属性是否有效(无需运行UI)我可以在单元测试中调用?

我正在寻找能够获取视图的内容,然后循环遍历所有WPF控件,对于每个WPF控件,它将查看所​​有绑定并检查它们是否有效.


顺便提一下,关于如何使OnPropertyChanged安全,和/或如何测试它有一些很好的问题(但是这些完成后可以达到WPF视图的水平.)


我已经对这个问题给予了赏金,因为有人必须认真思考这个问题并提出解决方案.

Ber*_*rmo 5

我想我已经想出了一些可以使用简单反射工作的东西,并调整了我过去使用的一些代码(该FindBindingsRecursively方法的代码由Martin Bennedik编写,作为他的Enterprise WPF验证控件的一部分):

[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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)