2010-01-19 10 views
7

Wir haben versucht, Einheitentests für eine in C# geschriebenen Worker-Klasse zu schreiben, die eine API von Drittanbietern (COM-basiert) mit Moq zum dynamischen Erstellen der Mock-Objekte spottet. NUnit ist unser Unit-Testing-Framework.Mocking 3rd Party Callback-Ereignisse mit moq

Diese Komponente von Drittanbietern implementiert einige Schnittstellen, muss aber auch mithilfe von Ereignissen in unsere Worker-Klasse zurückrufen. Unser Plan war, die Ereignisse zu simulieren, die diese 3rd-Party-Komponente auslösen kann, und zu testen, ob unsere Arbeiterklasse wie erwartet funktioniert.

Leider sind wir auf ein Problem gestoßen, bei dem moq anscheinend nicht in der Lage ist, extern definierte Ereignisse auszulösen und anzuheben. Leider kann ich den Code für die genaue API von Drittanbietern, die wir verwenden, nicht angeben, aber wir haben das Problem mit der MS Word-API neu erstellt und gezeigt, wie die Tests funktionieren, wenn eine lokal definierte Schnittstelle verwendet wird:

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

Könnte jemand erklären, warum das Aufrufen von Ereignissen für die lokal definierte Schnittstelle funktioniert, aber nicht das von der API der dritten Partei (in diesem Fall Word) importierte?

Ich habe das Gefühl, dass dies mit der Tatsache zu tun hat, dass wir mit einem COM-Objekt (über die Interop-Assembly) sprechen, aber nicht sicher sind, wie das Problem zu umgehen ist.

+1

Es scheint, dass dieser Fehler in Moq v4.0 behoben wurde: http://code.google.com/p/moq/issues/detail?id=226 –

Antwort

14

Moq "fängt" Ereignisse ab, indem es Aufrufe an die internen Methoden eines Ereignisses erkennt. Diese Methoden heißen add_ + der Name des Ereignisses und sind insofern "speziell", als es sich um nicht standardmäßige C# -Methoden handelt. Events sind ein wenig wie Eigenschaften (get/set) und kann wie folgt definiert werden:

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

Wenn wurde das obige Ereignis auf einer Schnittstelle definiert sein Moq'd, würde der folgende Code verwendet werden, dieses Ereignis zu erhöhen:

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

Da es nicht möglich ist in C# Ereignisse direkt zu referenzieren, Moq fängt alle Verfahren auf dem Mock rufen und Tests, um zu sehen, ob der Anruf eine Ereignisbehandlungsroutine (im obigen Fall hinzuzufügen war, ein null-Handler hinzugefügt). Wenn dies der Fall ist, kann indirekt eine Referenz als das "Ziel" der Methode erhalten werden.

Eine Ereignisbehandlungsmethode wird von Moq mit Reflektion als Methode erkannt, die mit dem Namen add_ beginnt und mit dem IsSpecialName Flag gesetzt ist. Diese zusätzliche Prüfung dient dazu, Methodenaufrufe herauszufiltern, die nicht mit Ereignissen in Zusammenhang stehen, sondern einen Namen haben, der mit add_ beginnt.

In dem Beispiel würde die abgefangene Methode add_MyEvent heißen und hätte das Flag IsSpecialName gesetzt.

Allerdings scheint es, dass dies für Schnittstellen in Interops definiert nicht ganz richtig ist, als auch wenn der Event-Handler-Methode Namen mit add_ beginnt, tut es nicht haben die IsSpecialName-Flag gesetzt. Dies liegt möglicherweise daran, dass die Ereignisse über Code niedrigerer Ebene zu (COM) -Funktionen vermittelt werden und nicht als "spezielle" C# -Ereignisse.

Dieser (nach Ihrem Beispiel) mit dem folgenden NUnit-Test nachgewiesen werden kann:

MethodInfo interopMethod = typeof(ApplicationEvents4_Event).GetMethod("add_WindowActivate"); 
MethodInfo localMethod = typeof(LocalInterface_Event).GetMethod("add_WindowActivate"); 

Assert.IsTrue(interopMethod.IsSpecialName); 
Assert.IsTrue(localMethod.IsSpecialName); 

Darüber hinaus kann eine Schnittstelle nicht erstellt werden, die das von Interop-Schnittstelle erben das Problem zu umgehen, wie es auch die erbt Marshalled add/remove Methoden.

Dieses Problem wurde hier auf der Moq issue tracker berichtet: http://code.google.com/p/moq/issues/detail?id=226

Update:

Bis dies durch die Moq Entwickler angesprochen wird, kann die einzige Lösung das zu ändern sein Reflektion zu verwenden Schnittstelle, die den Zweck der Verwendung von Moq zu besiegen scheint. Leider ist es vielleicht besser, nur für diesen Fall einen eigenen Moq zu erstellen.

Dieses Problem wurde in Moq 4.0 behoben (freigegeben August 2011).

+0

Danke - wird die moq Bug-Berichte im Auge behalten . Wir haben am Ende nur eine einfache Wrapper-Klasse um die API von Drittanbietern geschrieben, die wir dann mithilfe von Komponententests ausspionieren konnten. –

+0

Vielen Dank für das Aufspüren. Ich habe das gleiche Problem, außer es war mit Schnittstellen in F # definiert. Es stellt sich heraus, dass sie auch das Flag IsSpecialName auslassen und auch in diesem Bug weitermachen. – JaredPar

-1

Können Sie die COM-Schnittstelle von der dritten Partei neu definieren und mit moq verwenden.

Es scheint, Ihre Absicht ist es, die externe Abhängigkeit Moq weg moq und nicht gut mit der COMInterop Assembly zu spielen, sollten Sie in der Lage sein, Reflektor zu öffnen und beliebige Schnittstelle Definitionen aus der Interop-Assembly zu ziehen, definieren die Mock und Führen Sie Ihre Komponententests durch