Objets moqueurs avec Moq lorsque le constructeur a des paramètres

92

J'ai un objet que j'essaye de me moquer en utilisant moq. Le constructeur de l'objet a des paramètres obligatoires:

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

Maintenant, j'essaie de créer la maquette pour cet objet en utilisant la syntaxe v3 "setup" ou v4 "Mock.Of" de moq mais je n'arrive pas à comprendre cela ... tout ce que j'essaye ne valide pas. Voici ce que j'ai jusqu'à présent, mais la dernière ligne me donne un objet réel, pas le simulacre. La raison pour laquelle je fais cela est parce que j'ai des méthodes sur le CustomerSyncEngine que je veux vérifier qu'elles sont appelées ...

// 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
la source
Pouvez-vous fournir un exemple de méthode dont vous souhaitez vérifier l'appel?
Ciaran
4
Donc, si j'ai des dépendances sur les classes plutôt que sur les interfaces, je dois me moquer même de leurs dépendances, cela diminue de manière récursive. En fin de compte, je suis obligé d'utiliser certaines interfaces pour garder mon code testable, même si je n'ai pas besoin des interfaces dans mon code. Je pense que trop d'interfaces est une plus grande odeur que de se moquer des classes de béton ...
Tarion

Réponses:

34

La dernière ligne vous donne une instance réelle parce que vous utilisez le nouveau mot-clé, et non vous moquez de CustomerSyncEngine.

Tu devrais utiliser Mock.Of<CustomerSyncEngine>()

Le seul problème avec les types Mocking Concrete est que Moq aurait besoin d'un constructeur par défaut public (sans paramètres) OU vous devez créer le Moq avec la spécification arg du constructeur. http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

La meilleure chose à faire serait de faire un clic droit sur votre classe et de choisir Extraire l'interface.

Raghu
la source
3
En ce qui concerne le problème, une alternative consiste à utiliser un conteneur AutoMocking. Mon préféré est Machine.Fakes en conjonction avec Machine.Specifications, l'utilisation d'un conteneur à verrouillage automatique facilite le test de petites surfaces. Supposons qu'Andrew ait besoin de tester une méthode dans la mesure CustomerSyncEngineoù seules les utilisations ICrmProvideravec des implémentations de simulation traditionnelles doivent être fournies pour les 3 interfaces, alors qu'un conteneur de verrouillage automatique ne vous permettrait d'en fournir qu'une.
Chris Marisic
73

Remplacez la dernière ligne par

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

et ça devrait marcher

Suhas
la source
3
Vous ne savez pas comment ce commentaire s'applique à ma réponse?
Suhas
2
Parce que cela provoquerait une erreur de compilation en tant que mockLogger et d'autres lèveront une exception selon laquelle ils n'ont pas de propriété Object
Justin Pihony
2
Étant donné que l'OP utilise Mock.Of <T> () pour créer des simulations des types de journaliseur, de crm et de cache, l'objet renvoyé est renvoyé en tant que T, et non en tant que Mock <T>. Ainsi, mockLogger.Object etc. n'est pas nécessaire lorsque vous les donnez au Mock of CustomerSyncEngine et, comme @JustinPihony l'a mentionné, devrait vous montrer une erreur de conception.
Josh Gust
1
@suhas ne devrait pas son êtrenew Mock<CustomerSyncEngine>(new object[]{mockLogger, mockCrm, mockCache}).Object;
GiriB
@GiriB n'est pas nécessaire, mais possible, car la maquette est définie avec Params. public Mock (objet params [] args)
Jiří Herník