使用Moq模拟第3方回调事件 [英] Mocking 3rd party callback events using moq

查看:89
本文介绍了使用Moq模拟第3方回调事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们一直在尝试为用C#编写的工作者类编写单元测试,该类使用moq来模拟第三方API(基于COM)以动态创建模拟对象. NUnit是我们的单元测试框架.

We've been trying write unit tests for a worker class written in C#, which mocks out a third party API (COM based) using moq to dynamically create the mock objects. NUnit is our unit testing framework.

此第三方组件实现了几个接口,但还需要使用事件回调到我们的worker类中.我们的计划是模拟此第三方组件可能引发的事件,并测试我们的工人阶级是否按预期运作.

This third party component implements a couple of interfaces, but also needs to call back into our worker class using events. Our plan was to simulate the events that this 3rd party component can raise, and test that our worker class operated as expected.

不幸的是,我们遇到了一个问题,起订量似乎无法模拟并引发外部定义的事件.不幸的是,我无法提供我们正在使用的确切第三方API的代码,但是我们已经使用MS Word API重新创建了该问题,并且还展示了使用本地定义的接口时测试的工作方式:

Unfortunately we've run into a problem in that moq seems unable to mock out and raise events that are defined externally. Unfortunately I can't provide the code for the exact 3rd party API we're using, but we've recreated the issue using the MS Word API, and also shown how the tests work when when using a locally defined interface:

using Microsoft.Office.Interop.Word;
using Moq;
using NUnit.Framework;
using SeparateNamespace;

namespace SeparateNamespace
{
    public interface LocalInterface_Event
    {
        event ApplicationEvents4_WindowActivateEventHandler WindowActivate;
    }
}

namespace TestInteropInterfaces
{
    [TestFixture]
    public class Test
    {
        [Test]
        public void InteropExample()
        {
            // from interop
            Mock<ApplicationEvents4_Event> mockApp = new Mock<ApplicationEvents4_Event>();

            // identical code from here on...
            bool isDelegateCalled = false;

            mockApp.Object.WindowActivate += delegate { isDelegateCalled = true; };

            mockApp.Raise(x => x.WindowActivate += null, null, null);

            Assert.True(isDelegateCalled);
        }

        [Test]
        public void LocalExample()
        {
            // from local interface
            Mock<LocalInterface_Event> mockApp = new Mock<LocalInterface_Event>();

            // identical code from here on...
            bool isDelegateCalled = false;

            mockApp.Object.WindowActivate += delegate { isDelegateCalled = true; };

            mockApp.Raise(x => x.WindowActivate += null, null, null);

            Assert.True(isDelegateCalled);
        }
    }
}

谁能解释为什么引发针对本地定义的接口的事件有效但不能从第三方API(在本例中为Word)导入的事件的原因?

Could anyone explain why raising events for the locally defined interface works but not the one imported from the 3rd party API (in this case Word)?

我觉得这与我们正在与COM对象(通过互操作程序集)进行交谈有关,但是不确定如何解决该问题.

I have a feeling that this is to do with the fact we are talking to a COM object (via the interop assembly) but am not sure how to work around the problem.

推荐答案

通过检测对事件内部方法的调用,Moq可以拦截"事件.这些方法被命名为add_ +事件名称,并且是特殊的,因为它们是非标准的C#方法.事件有点像属性(get/set),可以定义如下:

Moq 'intercepts' events by detecting calls to an event's internal methods. These methods are named add_ + the event name and are 'special' in that they are non-standard C# methods. Events are somewhat like properties (get/set) and can be defined as follows:

event EventHandler MyEvent
{
    add { /* add event code */ };
    remove { /* remove event code */ };
}

如果以上事件在接口上定义为Moq,则将使用以下代码引发该事件:

If the above event was defined on an interface to be Moq'd, the following code would be used to raise that event:

var mock = new Mock<IInterfaceWithEvent>;
mock.Raise(e => e.MyEvent += null);

由于在C#中不可能直接引用事件,因此Moq会在Mock上拦截所有方法调用,并测试该调用是否要添加事件处理程序(在上述情况下,将添加空处理程序).如果是这样,则可以间接获得参考作为该方法的目标".

As it is not possible in C# to reference events directly, Moq intercepts all method calls on the Mock and tests to see if the call was to add an event handler (in the above case, a null handler is added). If so, a reference can be indirectly obtained as the 'target' of the method.

Moq使用反射将事件处理程序方法检测为方法,该方法以名称add_IsSpecialName标志开始.这项额外的检查是过滤掉与事件无关但名称以add_开头的方法调用.

An event handler method is detected by Moq using reflection as a method beginning with the name add_ and with the IsSpecialName flag set. This extra check is to filter out method calls unrelated to events but with a name starting add_.

在此示例中,被拦截的方法将称为add_MyEvent,并将设置IsSpecialName标志.

In the example, the intercepted method would be called add_MyEvent and would have the IsSpecialName flag set.

但是,似乎对于interops中定义的接口而言,这并不完全正确,因为事件处理程序方法的名称以add_开头,但 not 却没有设置IsSpecialName标志.这可能是因为这些事件是通过低级代码编组到(COM)函数的,而不是真正的特殊" C#事件.

However, it seems that this is not entirely true for interfaces defined in interops, as although the event handler method's name starts with add_, it does not have the IsSpecialName flag set. This may be because the events are being marshalled via lower-level code to (COM) functions, rather than being true 'special' C# events.

可以通过以下NUnit测试来显示(按照您的示例):

This can be shown (following your example) with the following NUnit test:

MethodInfo interopMethod = typeof(ApplicationEvents4_Event).GetMethod("add_WindowActivate");
MethodInfo localMethod = typeof(LocalInterface_Event).GetMethod("add_WindowActivate");

Assert.IsTrue(interopMethod.IsSpecialName);
Assert.IsTrue(localMethod.IsSpecialName);

此外,无法创建继承自interop接口的接口来解决此问题,因为它还将继承编组的add/remove方法.

Furthermore, an interface cannot be created which inherits the from interop interface to workaround the problem, as it will also inherit the marshalled add/remove methods.

此问题已在Moq问题跟踪器中报告,网址为: http://code.google.com/p/moq/issues/detail?id = 226

This issue was reported on the Moq issue tracker here: http://code.google.com/p/moq/issues/detail?id=226

更新:

在Moq开发人员解决此问题之前,唯一的解决方法可能是使用反射来修改接口,这似乎无法使用Moq.不幸的是,对于这种情况,最好是按自己的方式"起订量.

此问题已在Moq 4.0(2011年8月发布)中得到解决.

This issue has been fixed in Moq 4.0 (Released Aug 2011).

这篇关于使用Moq模拟第3方回调事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆