代码之家  ›  专栏  ›  技术社区  ›  John Sibly

使用moq模拟第三方回调事件

  •  7
  • John Sibly  · 技术社区  · 14 年前

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

    这个第三方组件实现了几个接口,但也需要使用事件回调到我们的工人类。我们的计划是模拟第三方组件可以引发的事件,并测试我们的工人阶级按预期运行。

    不幸的是,我们遇到了一个问题,即能源部似乎无法模拟并提出外部定义的事件。不幸的是,我无法提供我们使用的确切第三方API的代码,但我们已使用MS Word API重新创建了该问题,并展示了使用本地定义的接口时测试的工作方式:

    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)?

    我有一种感觉,这与我们正在与一个COM对象(通过interop程序集)交谈的事实有关,但我不确定如何解决这个问题。

    2 回复  |  直到 8 年前
        1
  •  14
  •   g t Omri Btian    8 年前

    moq通过检测对事件内部方法的调用来“截获”事件。这些方法被命名为 add_ +事件名和是“特殊的”,因为它们是非标准的C方法。事件有点像属性( get / set )其定义如下:

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

    如果上述事件是在一个接口上定义为moq'd的,那么将使用以下代码引发该事件:

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

    由于在C中不可能直接引用事件,因此MOQ会截获模拟和测试上的所有方法调用,以查看调用是否要添加事件处理程序(在上面的情况下,会添加一个空处理程序)。如果是这样,就可以间接地获得一个引用作为方法的“目标”。

    MOQ使用反射作为以名称开头的方法检测事件处理程序方法 阿达皮 并与 IsSpecialName 标志集。这个额外的检查是筛选出与事件无关但名称以 阿达皮 .

    在该示例中,将调用被截取的方法 add_MyEvent 而且会有 同名 标志集。

    但是,对于在互操作中定义的接口,这似乎不是完全正确的,尽管事件处理程序方法的名称以开头 添加_ 它确实如此 特殊名称 标志集。这可能是因为事件是通过较低级别的代码(COM)函数进行编组的,而不是真正的“特殊”C事件。

    这可以通过以下nunit测试显示(以您的示例为例):

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

    此外,无法创建继承From Interop接口以解决问题的接口,因为它还将继承已封送的 add / remove 方法。

    此问题在这里的MoQ问题跟踪程序中报告: http://code.google.com/p/moq/issues/detail?id=226

    更新:

    在MOQ开发人员解决这一问题之前,唯一的解决方法可能是使用反射来修改接口,这似乎违背了使用MOQ的目的。不幸的是,在这种情况下,最好是“滚你自己的”MoQ。

    该问题已在MoQ 4.0中得到解决(2011年8月发布)。

        2
  •  -1
  •   Andy    14 年前

    你能从第三方重新定义COM接口并与MOQ一起使用吗?

    似乎您的意图是moq消除外部依赖性,而moq没有很好地处理ComInterop程序集,您应该能够打开Reflector并从interop程序集中提取您想要的任何接口定义,定义模拟并运行单元测试。