Burlarse de objetos con Moq cuando el constructor tiene parámetros

92

Tengo un objeto que estoy tratando de simular usando moq. El constructor del objeto tiene parámetros obligatorios:

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

Ahora estoy tratando de crear el simulacro para este objeto usando la sintaxis de moq v3 "setup" o v4 "Mock.Of" pero no puedo resolver esto ... todo lo que estoy intentando no está validando. Esto es lo que tengo hasta ahora, pero la última línea me da un objeto real, no la simulación. La razón por la que hago esto es porque tengo métodos en CustomerSyncEngine que quiero verificar que se están llamando ...

// 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
fuente
¿Puede proporcionar un método de muestra que desee verificar que se llama?
Ciaran
4
Entonces, si tengo dependencias en clases en lugar de interfaces, tengo que burlarme incluso de sus dependencias, esto se reduce de forma recursiva. Al final, me veo obligado a usar algunas interfaces para mantener mi código comprobable, incluso si no necesito las interfaces en mi código. Creo que demasiadas interfaces es un olor más grande que burlarse de clases concretas ...
Tarion

Respuestas:

34

La última línea le da una instancia real porque está usando la nueva palabra clave, no burlándose de CustomerSyncEngine.

Deberías usar Mock.Of<CustomerSyncEngine>()

El único problema con los tipos de Mocking Concrete es que Moq necesitaría un constructor público predeterminado (sin parámetros) O necesita crear el Moq con la especificación arg del constructor. http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

Lo mejor que puede hacer es hacer clic derecho en su clase y elegir Extraer interfaz.

Raghu
fuente
3
En cuanto al problema, una alternativa es utilizar un contenedor AutoMocking. Mi favorito es Machine.Fakes junto con Machine.Specifications, el uso de un contenedor de embalaje automático hace que sea más fácil probar áreas de superficie más pequeñas. Suponga que Andrew necesita probar un método en el CustomerSyncEngineque solo se ICrmProviderdeben proporcionar los usos con implementaciones de simulación tradicionales para las 3 interfaces, mientras que un contenedor de simulación automática le permitiría proporcionar solo una.
Chris Marisic
73

Cambie la última línea a

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

y debería funcionar

Suhas
fuente
3
¿No estás seguro de cómo se aplica este comentario a mi respuesta?
Suhas
2
Porque esto causaría un error de compilación ya que mockLogger y otros lanzarán una excepción de que no tienen una propiedad de Objeto
Justin Pihony
2
Debido a que el OP está usando Mock.Of <T> () para crear simulaciones de los tipos de registrador, crm y caché, el objeto devuelto se devuelve como T, no como Mock <T>. Por lo tanto, mockLogger.Object, etc.no es necesario al entregarlos al Mock of CustomerSyncEngine y, como mencionó @JustinPihony, debería mostrarle un error de tiempo de diseño.
Josh Gust
1
@suhas no debería ser suyonew Mock<CustomerSyncEngine>(new object[]{mockLogger, mockCrm, mockCache}).Object;
GiriB
@GiriB no es necesario, pero es posible, ya que el simulacro se define con Params. public Mock (objeto params [] args)
Jiří Herník