Как я могу модульно протестировать то, что использует 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 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
возвращается ноль. Как я могу создать визуальное дерево, которое будет работать?
3 ответа
Вот почему статика проблематична.
Вы можете абстрагировать функциональность интерфейса и создать реализацию по умолчанию, которая использует статический метод. Затем вы можете использовать внедрение зависимостей, что делает этот модульный тест тривиальным - смоделируйте зависимость от 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.
Основываясь на этом ответе здесь о печати документов через 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);
}
Обратите внимание, что
- другой ответ использует API-интерфейс Reach-dll, который не похож на API, который я вижу. Я предполагаю, что есть различия между.NEt Framework версий 3.5 и 4.0
-
ToMaybeOf
материал в основном означает лечитьpageContent
какIAddChild
и сделать действие на этом интерфейсе - это не будет работать с элементом типа Window, так как этот элемент по существу добавляется как дочерний элемент к Visual, и Window будет горько жаловаться на это.
Чтобы смоделировать визуальное дерево, вам придется его создать и визуализировать. Таким образом, вам придется создать реальное окно, которое не идеально подходит для юнит-теста.