How can I unit test something that uses VisualTreeHelper?
Asked Answered
Y

3

10

I have this static helper function:

    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);
    }

It works in a real application, but I'm trying to write some unit tests for it. Here's my first attempt:

    [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);
    }

Unfortunately it fails because VisualTreeHelper is returning null. How can I mock up a visual tree that will work?

Yellowish answered 23/11, 2010 at 11:14 Comment(0)
I
2

This is why statics are problematic.

You can abstract the functionality behind an interface and create a default implementation that uses the static method. You can then use dependency injection, which makes this unit test trivial -- mock the dependency on IVisualTreeHelper or roll your own stub implementation that you can configure to return any value you assign.

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);
    }
}

Obviously, you may need to add other VisualTreeHelper methods to your interface and default implementation, if you're using other methods elsewhere.

It is still not completely clean because the unit you're testing is itself static, and you're going to run into exactly the same problem when you try to unit test any class that relies on your UIHelper class' static methods.

Ila answered 23/11, 2010 at 14:38 Comment(0)
C
3

Based on this answer here on printing documents via Wpf-controls and convert to XPS I came up with the following extension method to create the visual tree. It works well within NUnit without STA-thread or anything.

/// <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);
}

Please note that

  • the other answer uses an API of the Reach-dll that does not look like the API I am seeing. I assume that there are differences between .NEt Framework versions 3.5 and 4.0
  • the ToMaybeOf stuff basically means to treat pageContent as IAddChild and do an action on that interface
  • this will not work with an element of type Window since the element is essentially added as a child to a Visual and Window will complain bitterly about this.
Chainman answered 1/3, 2012 at 15:58 Comment(1)
I cast pageContent to IAddChild and performed the operation directly, rather than relying on the ToMaybeOf and Do: ((IAddChild)pageContent).AddChild(fixedPage);Oceanid
I
2

This is why statics are problematic.

You can abstract the functionality behind an interface and create a default implementation that uses the static method. You can then use dependency injection, which makes this unit test trivial -- mock the dependency on IVisualTreeHelper or roll your own stub implementation that you can configure to return any value you assign.

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);
    }
}

Obviously, you may need to add other VisualTreeHelper methods to your interface and default implementation, if you're using other methods elsewhere.

It is still not completely clean because the unit you're testing is itself static, and you're going to run into exactly the same problem when you try to unit test any class that relies on your UIHelper class' static methods.

Ila answered 23/11, 2010 at 14:38 Comment(0)
P
-1

To mock a visual tree you will have to actually create and render one. So you will have to create an actual window, wich isn't particulary ideal for a unit test.

Pricket answered 23/11, 2010 at 14:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.