Насмешка сторонних событий обратного вызова с помощью moq

Мы пробовали модульные тесты записи на класс рабочего, записанный в C#, который дразнит третье лицо API (базирующийся COM) использующий moq для динамичного создания фиктивных объектов. NUnit является нашей платформой поблочного тестирования.

Этот сторонний компонент реализует несколько интерфейсов, но также и должен перезвонить в наши события использования класса рабочего. Наш план состоял в том, чтобы моделировать события, которые этот сторонний компонент может сгенерировать, и тест что наш класс рабочего, управляемый как ожидалось.

К сожалению, мы столкнулись с проблемой, в которой moq кажется не могущим дразнить и сгенерировать события, которые определяются внешне. К сожалению, я не могу предоставить код для точной третьей стороны 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 блок), но не уверено, как работать вокруг проблемы.

7
задан John Sibly 19 January 2010 в 21:25
поделиться

2 ответа

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

Кроме того, нельзя создать интерфейс, который наследует интерфейс from interop для решения проблемы, поскольку он также унаследует упорядоченное add / удалить методы.

Об этой проблеме было сообщено в системе отслеживания проблем Moq здесь: http://code.google.com/p/moq/issues/detail?id=226

Обновление:

Пока это не будет решено разработчиками Moq, единственным обходным решением может быть использование отражения для изменения интерфейса, которое, похоже, противоречит цели использования Moq. К сожалению, для этого случая может быть лучше просто «накатать свой собственный» Moq.

Эта проблема исправлена ​​в Moq 4.0 (от августа 2011 г.).

14
ответ дан 6 December 2019 в 19:36
поделиться

Можете ли вы переопределить интерфейс COM с 3-го вечеринки и использовать его с MOQ.

Кажется, что ваше намерение состоит в том, чтобы MOQ прочь Внешняя зависимость и MOQ красиво не играют с сборкой Cominterop, вы должны быть в состоянии открыть отражатель и вытащить какие-либо определения интерфейса, которые вы хотите из сборки Interop. Ваши модульные тесты

-1
ответ дан 6 December 2019 в 19:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: