使用最小起订量模拟第 3 方回调事件

发布于 2024-08-18 09:37:26 字数 1791 浏览 4 评论 0原文

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

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

不幸的是,我们遇到了一个问题,最小起订量似乎无法模拟并引发外部定义的事件。不幸的是,我无法提供我们正在使用的确切第 3 方 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);
        }
    }
}

任何人都可以解释吗为什么为本地定义的接口引发事件有效,但从第 3 方 API(在本例中为 Word)导入的事件无效?

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

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.

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.

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);
        }
    }
}

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

爱,才寂寞 2024-08-25 09:37:26

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 会拦截 Mock 上的所有方法调用并测试该调用是否用于添加事件处理程序(在上述情况下,空处理程序是额外)。如果是这样,则可以间接获得引用作为该方法的“目标”。

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

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

然而,对于互操作中定义的接口来说,这似乎并不完全正确,因为尽管事件处理程序方法的名称以 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);

此外,无法创建继承 interop 接口来解决问题的接口,因为它还将继承编组的 add/<代码>删除方法。

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

更新:

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

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

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 */ };
}

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

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.

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_.

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

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.

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

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.

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

Update:

Until this is addressed by the Moq developers, the only workaround may be to use reflection to modify the interface which seems to defeat the purpose of using Moq. Unfortunately it may be better just to 'roll your own' Moq for this case.

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

﹉夏雨初晴づ 2024-08-25 09:37:26

您可以从第 3 方重新定义 COM 接口并将其与最小起订量一起使用吗?

看来您的意图是最小起订量消除外部依赖项,并且最小起订量与 COMInterop 程序集配合得不好,您应该能够打开反射器并从互操作程序集中提取您想要的任何接口定义,定义模拟并运行单元测试

Can you redefine the COM interface from the 3rd party and use it with moq.

It seems your intention is to moq away the external dependency and moq isn't playing nicely with the COMInterop assembly, you should be able to open reflector and pull any interface definitions you want from the interop assembly, define the mock and run your unit tests

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文