Objekte mit Moq verspotten, wenn der Konstruktor Parameter hat

91

Ich habe ein Objekt, das ich mit moq verspotten möchte. Der Konstruktor des Objekts hat folgende Parameter:

public class CustomerSyncEngine {
    public CustomerSyncEngine(ILoggingProvider loggingProvider, 
                              ICrmProvider crmProvider, 
                              ICacheProvider cacheProvider) { ... }
}

Jetzt versuche ich, den Mock für dieses Objekt entweder mit der v3 "setup" - oder der v4 "Mock.Of" -Syntax von moq zu erstellen, kann dies aber nicht herausfinden ... alles, was ich versuche, wird nicht validiert. Folgendes habe ich bisher, aber die letzte Zeile gibt mir ein echtes Objekt, nicht den Schein. Der Grund, warum ich das mache, ist, dass ich Methoden in der CustomerSyncEngine habe, deren Überprüfung ich aufrufen möchte ...

// setup
var mockCrm = Mock.Of<ICrmProvider>(x => x.GetPickLists() == crmPickLists);
var mockCache = Mock.Of<ICacheProvider>(x => x.GetPickLists() == cachePickLists);
var mockLogger = Mock.Of<ILoggingProvider>();

// need to mock the following, not create a real class like this...
var syncEngine = new CustomerSyncEngine(mockLogger, mockCrm, mockCache);
Andrew Connell
quelle
Können Sie eine Beispielmethode angeben, deren Aufruf Sie überprüfen möchten?
Ciaran
4
Wenn ich also eher Abhängigkeiten von Klassen als von Schnittstellen habe, muss ich mich sogar über deren Abhängigkeiten lustig machen, dies geht rekursiv zurück. Am Ende bin ich gezwungen, einige Schnittstellen zu verwenden, um meinen Code testbar zu halten, auch wenn ich die Schnittstellen in meinem Code nicht benötige. Ich denke, zu viele Schnittstellen sind ein größerer Geruch als das Verspotten konkreter Klassen ...
Tarion

Antworten:

33

In der letzten Zeile erhalten Sie eine echte Instanz, da Sie das neue Schlüsselwort verwenden und CustomerSyncEngine nicht verspotten.

Du solltest benutzen Mock.Of<CustomerSyncEngine>()

Das einzige Problem bei Mocking Concrete-Typen besteht darin, dass Moq einen öffentlichen Standardkonstruktor (ohne Parameter) benötigt oder dass Sie den Moq mit der Konstruktor-Arg-Spezifikation erstellen müssen. http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

Am besten klicken Sie mit der rechten Maustaste auf Ihre Klasse und wählen Sie Schnittstelle extrahieren.

Raghu
quelle
3
In Bezug auf das Problem besteht eine Alternative darin, einen AutoMocking-Container zu verwenden. Mein Favorit ist Machine.Fakes in Verbindung mit Machine.Specifications. Die Verwendung eines Automocking-Containers erleichtert das Testen kleinerer Oberflächen. Angenommen, Andrew muss eine Methode testen, bei CustomerSyncEngineder nur die Verwendung ICrmProvidermit herkömmlichen Verspottungsimplementierungen für alle drei Schnittstellen bereitgestellt werden muss, während Sie mit einem Autmocking-Container nur eine bereitstellen können.
Chris Marisic
72

Ändern Sie die letzte Zeile in

var syncEngine = new Mock<CustomerSyncEngine>(mockLogger, mockCrm, mockCache).Object;

und es sollte funktionieren

Suhas
quelle
3
Sie sind sich nicht sicher, wie dieser Kommentar auf meine Antwort zutrifft?
Suhas
2
Da dies einen Kompilierungsfehler verursachen würde, werden mockLogger und andere eine Ausnahme auslösen, dass sie keine Object-Eigenschaft haben
Justin Pihony
2
Da das OP Mock.Of <T> () verwendet, um Mocks der Typen logger, crm und cache zu erstellen, wird das zurückgegebene Objekt als T und nicht als Mock <T> zurückgegeben. MockLogger.Object usw. wird also nicht benötigt, wenn Sie es dem Mock of CustomerSyncEngine übergeben, und sollte, wie @JustinPihony erwähnt, einen Entwurfszeitfehler anzeigen.
Josh Gust
1
@suhas Sollte nicht seinnew Mock<CustomerSyncEngine>(new object[]{mockLogger, mockCrm, mockCache}).Object;
GiriB
@GiriB nicht benötigt, aber möglich, da der Mock mit Params definiert ist. public Mock (params object [] args)
Jiří Herník