如何对使用VisualTreeHelper的内容进行单元测试?

10

我有这个静态辅助函数:

    public static DependencyObject GetParentObject(DependencyObject child)
    {
        if (child == null) return null;
        ContentElement contentElement = child as ContentElement;

        if (contentElement != null)
        {
            var parent = ContentOperations.GetParent(contentElement);
            if (parent != null) return parent;

            var fce = contentElement as FrameworkContentElement;
            return fce != null ? fce.Parent : null;
        }

        //if it's not a ContentElement, rely on VisualTreeHelper
        return VisualTreeHelper.GetParent(child);
    }

它在真实应用中能够工作,但是我想为它编写一些单元测试。这是我的第一次尝试:

    [Test]
    public void GetParentObject_returns_immediate_parent()
    {
        var contentControl = new ContentControl();
        var textBox = new TextBox();

        contentControl.BeginInit();
        contentControl.Content = textBox;
        contentControl.EndInit();

        var result = UIHelper.GetParentObject(textBox);
        Assert.AreSame(contentControl, result);
    }

很不幸,它失败了,因为VisualTreeHelper返回了null。我该如何模拟一个可行的可视化树?

3个回答

3

基于这篇关于通过Wpf控件打印文档并转换为XPS的答案,我创建了以下扩展方法来创建可视树。它在NUnit中可以很好地工作,无需STA线程或其他任何东西。

/// <summary>
/// Render a UIElement such that the visual tree is generated, 
/// without actually displaying the UIElement
/// anywhere
/// </summary>
public static void CreateVisualTree(this UIElement element)
{
    var fixedDoc = new FixedDocument();
    var pageContent = new PageContent();
    var fixedPage = new FixedPage();
    fixedPage.Children.Add(element);
    pageContent.ToMaybeOf<IAddChild>().Do(c => c.AddChild(fixedPage));
    fixedDoc.Pages.Add(pageContent);

    var f = new XpsSerializerFactory();
    var w = f.CreateSerializerWriter(new MemoryStream());
    w.Write(fixedDoc);
}

请注意,另一个答案使用了Reach-dll的API,但这个API与我看到的API不太一样。我认为.NET Framework版本3.5和4.0之间存在差异。
ToMaybeOf的内容基本上意味着把pageContent视为IAddChild并对该接口执行操作。
这将无法使用窗口类型的元素工作,因为该元素实际上被添加为Visual的子级,而Window将对此表示强烈抗议。

我将 pageContent 强制转换为 IAddChild 并直接执行了操作,而不依赖于 ToMaybeOfDo((IAddChild)pageContent).AddChild(fixedPage); - David Meredith

2
这就是为什么静态方法存在问题。
你可以通过接口抽象出功能,并创建一个默认实现来使用静态方法。然后,您可以使用依赖注入,使这个单元测试变得很简单 - 模拟对IVisualTreeHelper的依赖或编写自己的存根实现,您可以配置以返回任何您分配的值。
public class Foo
{
    static IVisualTreeHelper visualTreeHelper;

    static Foo()
    {
        Foo.visualTreeHelper = new FrameworkVisualTreeHelper();
    }

    public Foo(IVisualTreeHelper visualTreeHelper)
    {
        Foo.visualTreeHelper = visualTreeHelper;
    }

    public static DependencyObject GetParentObject(DependencyObject child)
   {
       if (child == null) return null;
       ContentElement contentElement = child as ContentElement;

       if (contentElement != null)
       {
           var parent = ContentOperations.GetParent(contentElement);
           if (parent != null) return parent;

           var fce = contentElement as FrameworkContentElement;
           return fce != null ? fce.Parent : null;
       }

       //if it's not a ContentElement, rely on the IVisualTreeHelper
       return visualTreeHelper.GetParent(child);
   }
}

public interface IVisualTreeHelper
{
    DependencyObject GetParent(DependencyObject reference);
}

public class FrameworkVisualTreeHelper : IVisualTreeHelper
{
    public DependencyObject GetParent(DependencyObject reference)
    {
        return VisualTreeHelper.GetParent(reference);
    }
}

显然,如果你在其他地方使用了其他的方法,你可能需要添加其他的VisualTreeHelper方法到你的接口和默认实现中。

但是,它仍然不是完全干净的,因为你正在测试的单元本身是静态的,当你尝试对依赖于你的UIHelper类的静态方法的任何类进行单元测试时,你将遇到完全相同的问题。


-1
为了模拟一个可视化树,您必须实际创建和渲染一个。因此,您将不得不创建一个实际的窗口,这对于单元测试来说并不是特别理想的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接