模拟`System.Windows.FrameworkElement`

时间:2018-02-23 14:04:57

标签: c# unit-testing moq

我在名为MouseWheelListenerViewManager的类中有以下方法:

protected static void PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        if (!e.Handled)
        {
            e.Handled = true;

            var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
            eventArg.RoutedEvent = UIElement.MouseWheelEvent;
            eventArg.Source = sender;

            UIElement ancestor = ((FrameworkElement)sender).Parent as UIElement;
            while (ancestor != null)
            {
                ancestor.RaiseEvent(eventArg);
                ancestor = VisualTreeHelper.GetParent(ancestor) as UIElement;
            }
        }
}

我想测试.Parent sender被调用(以及所有其他祖先)。

我写了这个单元测试:

[Test]
public void PreviewMouseWheel_is_sent_to_ancestors()
  {
        TestableMouseWheelViewManager mouseWheelListenerViewManager = new TestableMouseWheelViewManager();
        BorderedCanvas canvas = mouseWheelListenerViewManager.CreateView(new ThemedReactContext(new ReactContext()));
        mouseWheelListenerViewManager.AddListeners(canvas);
        MouseDevice mouseDev = InputManager.Current.PrimaryMouseDevice;
        Mock<MouseWheelEventArgs> me = new Mock<MouseWheelEventArgs>(mouseDev, 0, 0);
        me.Object.RoutedEvent = UIElement.MouseWheelEvent;

        ///start of most relevant code

        Mock<FrameworkElement> sender = new Mock<FrameworkElement>();
        Mock<StackPanel> parent = new Mock<StackPanel>();
        Mock<StackPanel> grandParent = new Mock<StackPanel>();
        parent.Object.Children.Add(sender.Object);
        grandParent.Object.Children.Add(parent.Object);
        TestableMouseWheelViewManager.PreviewMouseWheel(sender, me.Object);

        ///end of most relevant code

        parent.Verify(mock => mock.RaiseEvent(It.IsAny<RoutedEventArgs>()), Times.Once());
        grandParent.Verify(mock => mock.RaiseEvent(It.IsAny<RoutedEventArgs>()), Times.Once());
}

但是,测试代码会抛出此异常:

System.NullReferenceException : Object reference not set to an instance of an object.

在这一行:

parent.Object.Children.Add(sender.Object);

我没有设置Children,而是试图设置Parent,如下所示:

sender.Parent = parent;

但是,这是不可能的,因为Parent的{​​{1}}是只读的。

那么如何模拟FrameworkElement继承链?

1 个答案:

答案 0 :(得分:0)

如何测试代码

Moq只会在您尝试模拟具体课程时给您带来痛苦,正如您在此处所做的那样(FrameworkElement),正如您所发现的那样。

相反,您应该考虑使用具体FrameworkElement对象的实例编写测试。值得庆幸的是,实例化FrameworkElement并不需要大量依赖。

为了验证调用FrameworkElement.RaiseEvent()方法,您可以使用处理程序订阅每个FrameworkElement.MouseWheel事件,该处理程序将标记它已被调用。

FrameworkElement sender = new FrameworkElement();
FrameworkElement parent = new FrameworkElement();
FrameworkElement grandParent = new FrameworkElement();

parent.Children.Add(sender);
grandParent.Children.Add(parent);

Dictionary<FrameworkElement, bool> callFlags = { 
    {sender, False},
    {parent, False},
    {grandParent, False} 
};
var handler = (sndr, e) => { callFlags[(sndr as FrameworkElement)] = True };

sender.MouseWheel += handler;
parent.MouseWheel += handler;
grandParent.MouseWheel += handler;

/* run tests */

// assert all call flags have been set to true
Assert.IsTrue(callFlags.All(cf => cf), "A MouseWheel event in the hierarchy was not raised");

(我目前无法测试此代码,所以希望它足以提供一个想法)

为什么你的问题出现错误(奖金)

同样,您可能无法将Moq与具体的FrameworkElement对象一起使用。我将简单解释为什么你收到你报告的错误。

在Moq中,new Mock<T>(MockBehavior)采用MockBehavior参数。 This a really good source on what this does。该参数是可选

要点是,因为您调用Mock<T>的默认构造函数,所传递的行为是MockBehavior.Default。这意味着,在没有特定setup()的情况下,当您调用Mock<T>.Object的阴影成员时,Moq将为其各自的类型实例化默认值,例如如果您呼叫int成员,则Moq返回0bool成员返回False,复杂类型成员返回null

现在你的代码(摘录):

Mock<FrameworkElement> sender = new Mock<FrameworkElement>();
Mock<StackPanel> parent = new Mock<StackPanel>();
parent.Object.Children.Add(sender.Object);

由于parent已使用MockBehavior.Default进行了实例化,并且由于您从未调用parent.Setup(p => p.Children).Returns(...),因此您获得了:

parent.Object.Children == null
// True

请注意,在Moq中,当您模拟某个类型时,您需要评估不同MockBehavior值的优点,并调用Setup()或保留行为以决定每个成员将在您的测试方法中调用。