2010-01-19 26 views
7

Hemos estado intentando pruebas de unidad de escritura para una clase de trabajador escrita en C#, que burla una API de terceros (basada en COM) utilizando moq para crear dinámicamente los objetos simulados. NUnit es nuestro marco de pruebas unitarias.Mocking eventos de devolución de llamada de terceros usando moq

Este componente de terceros implementa un par de interfaces, pero también necesita volver a llamar a nuestra clase de trabajadores utilizando eventos. Nuestro plan fue simular los eventos que puede generar este componente de terceros y probar que nuestra clase de trabajadores funcionó como se esperaba.

Desafortunadamente nos hemos encontrado con un problema en el que moq parece incapaz de simular y generar eventos que están definidos externamente. Desafortunadamente no puedo proporcionar el código para la API de terceros exacta que estamos utilizando, pero hemos recreado el problema utilizando la API de MS Word y también se muestra cómo funcionan las pruebas cuando se utiliza una interfaz definida localmente:

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

¿Alguien podría explicar por qué funciona la generación de eventos para la interfaz definida localmente pero no la importada de la API de terceros (en este caso, Word)?

Tengo la sensación de que esto tiene que ver con el hecho de que estamos hablando con un objeto COM (a través del conjunto de interoperabilidad) pero no estoy seguro de cómo solucionar el problema.

+1

Parece que este error se ha corregido en Moq v4.0: http://code.google.com/p/moq/issues/detail?id=226 –

Respuesta

14

Moq 'intercepta' eventos mediante la detección de llamadas a los métodos internos de un evento. Estos métodos se llaman add_ + el nombre del evento y son 'especiales' en el sentido de que son métodos de C# no estándar. Los eventos son algo así como propiedades (get/set) y pueden definirse como sigue:

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

Si el evento anterior se definió en una interfaz para ser Moq'd, el siguiente código se utiliza para elevar este caso:

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

Como no es posible en C# para hacer referencia a eventos directamente, Moq intercepta todas las llamadas de método en el Mock y pruebas para ver si la llamada fue agregar un controlador de eventos (en el caso anterior, un controlador nula es adicional). Si es así, una referencia se puede obtener indirectamente como el 'objetivo' del método.

Moq detecta un método de controlador de eventos utilizando la reflexión como un método que comienza con el nombre add_ y con el indicador IsSpecialName establecido. Esta comprobación adicional consiste en filtrar llamadas de método no relacionadas con eventos pero con un nombre que comienza en add_.

En el ejemplo, el método interceptado se llamaría add_MyEvent y tendría el indicador IsSpecialName establecido.

Sin embargo, parece que esto no es del todo cierto para las interfaces definidas en Interops, ya que aunque el nombre del manejador de eventos comienza con add_, lo hace no tiene definido el IsSpecialName bandera. Esto puede deberse a que los eventos se organizan a través de un código de nivel inferior para funciones (COM), en lugar de ser verdaderos eventos "especiales" de C#.

Esto se puede demostrar (siguiendo su ejemplo) con la siguiente prueba 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); 

Además, una interfaz no puede ser creado, que hereda los de la interfaz de interoperabilidad Para solucionar el problema, ya que también heredará el marshalled add/remove métodos.

Este problema se informó sobre el seguimiento de incidencias Moq aquí: http://code.google.com/p/moq/issues/detail?id=226

Actualización:

Hasta que esto sea abordado por los desarrolladores Moq, la única solución puede ser la de utilizar la reflexión para modificar el interfaz que parece frustrar el propósito de usar Moq. Desafortunadamente, puede ser mejor simplemente 'rodar tu propio' Moq para este caso.

Este problema se ha solucionado en Moq 4.0 (publicado en agosto de 2011).

+0

Gracias - vigilaremos los informes de errores moq . Terminamos simplemente escribiendo una clase contenedora simple alrededor de la API de terceros, que luego pudimos simular usando pruebas unitarias. –

+0

Gracias por rastrear esto. Me encontraba con el mismo problema, excepto que estaba con interfaces definidas en F #. Resulta que también omiten el indicador IsSpecialName y se ejecutan de cabeza en este error también. – JaredPar

-1

Puede redefinir la interfaz COM de un tercero y usarla con moq.

Parece que su intención es eliminar la dependencia externa y moq no está funcionando bien con el ensamble COMInterop, debería poder abrir el reflector y extraer las definiciones de interfaz que desee del ensamblado de interoperabilidad, definir el simulacro y ejecute las pruebas de su unidad

Cuestiones relacionadas