c# - 使用最小起订量模拟第三方回调事件

标签 c# unit-testing nunit mocking moq

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

这个第三方组件实现了几个接口(interface),但也需要使用事件回调我们的工作类。我们的计划是模拟该第 3 方组件可以引发的事件,并测试我们的 worker 类是否按预期运行。

不幸的是,我们遇到了一个问题,因为最小起订量似乎无法模拟和引发外部定义的事件。不幸的是,我无法提供我们正在使用的确切第 3 方 API 的代码,但我们已经使用 MS Word API 重现了该问题,并且还展示了在使用本地定义的接口(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);
        }
    }
}

谁能解释为什么为本地定义的接口(interface)引发事件有效,而不是从第三方 API(在本例中为 Word)导入的事件?

我感觉这与我们正在与 COM 对象对话(通过互操作程序集)这一事实有关,但我不确定如何解决该问题。

最佳答案

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

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

如果上述事件是在一个接口(interface)上定义的,则将使用以下代码来引发该事件:

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

由于在 C# 中无法直接引用事件,Moq 会拦截 Mock 上的所有方法调用并测试该调用是否要添加事件处理程序(在上述情况下,添加了空处理程序)。如果是,则可以间接获取引用作为方法的“目标”。

Moq 使用反射检测事件处理程序方法,作为以名称 add_ 开头并设置了 IsSpecialName 标志的方法。这个额外的检查是为了过滤掉与事件无关但名称以 add_ 开头的方法调用。

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

然而,对于互操作中定义的接口(interface)来说,这似乎并不完全正确,因为尽管事件处理程序方法的名称以 add_ 开头,但它没有 IsSpecialName 标志集。这可能是因为事件是通过较低级别的代码编码到 (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 接口(interface)的接口(interface)来解决该问题,因为它还将继承编码的 add/remove 方法。

此问题已在此处的最小起订量问题跟踪器上报告: http://code.google.com/p/moq/issues/detail?id=226

更新:

在 Moq 开发人员解决此问题之前,唯一的解决方法可能是使用反射来修改界面,这似乎违背了使用 Moq 的目的。不幸的是,对于这种情况,最好只“推出自己的”最小起订量。

此问题已在 Moq 4.0(2011 年 8 月发布)中得到修复。

关于c# - 使用最小起订量模拟第三方回调事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2097205/

相关文章:

c# - LINQ to SQL - 没有可用的添加方法

c# - Xamarin 安卓 : Save Facebook Access Token in strings. xml

javascript - 我如何测试这个 Angular Controller ?

android - ActivityTestRule 未启动 androidTest 中的 Activity

javascript - 使用 PouchDB 服务 promise 进行 AngularJS 单元测试

unit-testing - 如何让 NUnit 测试列表中的所有值,而不仅仅是第一个失败

c# - 将日期时间字符串与阿拉伯字符串连接起来

c# - 带有模板的 EPPlus 未按预期工作

c# - 对象未设置为犀牛模拟中对象的实例

testing - 如何开始构建使用 TypeMock ?