Пересмешивание сторонних событий обратного вызова с использованием moq - PullRequest
7 голосов
/ 20 января 2010

Мы пытались написать модульные тесты для рабочего класса, написанного на 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-объектом (через сборку взаимодействия), но я не уверен, как обойти эту проблему.

Ответы [ 2 ]

14 голосов
/ 21 января 2010

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

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

Эта проблема была зарегистрирована на трекере проблем Moq здесь: http://code.google.com/p/moq/issues/detail?id=226

Обновление:

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

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

0 голосов
/ 20 января 2010

Можете ли вы переопределить интерфейс COM от стороннего производителя и использовать его с moq.

Похоже, вы намереваетесь удалить внешнюю зависимость, а moq не очень хорошо работает со сборкой COMInterop, вы должны иметь возможность открыть отражатель и извлечь любые определения интерфейса из сборки взаимодействия, определить макет и запустить ваши юнит-тесты

...