我们一直在尝试为用 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/