2011-10-05 16 views
41

Tengo una clase que estoy probando que tiene como dependencia otra clase (una instancia de la cual pasa al método init de la CUT). Quiero simular esta clase usando la biblioteca Python Mock.Objeto Python Mock con un método llamado varias veces

Lo que tengo es algo así como:

mockobj = Mock(spec=MyDependencyClass) 
mockobj.methodfromdepclass.return_value = "the value I want the mock to return" 
assertTrue(mockobj.methodfromdepclass(42), "the value I want the mock to return") 

cutobj = ClassUnderTest(mockobj) 

Lo cual está bien, pero "methodfromdepclass" es un método con parámetros, y como tal Quiero crear un único objeto de burla, donde dependiendo de qué argumentos se pasan a methodfromdepclass devuelve diferentes valores.

La razón por la que deseo este comportamiento parametrizado es que quiero crear varias instancias de ClassUnderTest que contengan valores diferentes (cuyos valores se producen por lo que devuelve el mockobj).

Un poco lo que pienso (esto por supuesto no funciona):

mockobj = Mock(spec=MyDependencyClass) 
mockobj.methodfromdepclass.ifcalledwith(42).return_value = "you called me with arg 42" 
mockobj.methodfromdepclass.ifcalledwith(99).return_value = "you called me with arg 99" 

assertTrue(mockobj.methodfromdepclass(42), "you called me with arg 42") 
assertTrue(mockobj.methodfromdepclass(99), "you called me with arg 99") 

cutinst1 = ClassUnderTest(mockobj, 42) 
cutinst2 = ClassUnderTest(mockobj, 99) 

# now cutinst1 & cutinst2 contain different values 

¿Cómo lograr este tipo "ifcalledwith" de la semántica?

Respuesta

60

Trate side_effect

def my_side_effect(*args, **kwargs): 
    if args[0] == 42: 
     return "Called with 42" 
    elif args[0] == 43: 
     return "Called with 43" 
    elif kwarg['foo'] == 7: 
     return "Foo is seven" 

mockobj.mockmethod.side_effect = my_side_effect 
+4

Impresionante, exactamente lo que necesitaba. No es un fan de la sintaxis, pero funciona increíble. ¡Gracias! –

+1

Una advertencia que me gustaría añadir aquí en la que corrí cuando uso esta solución, que funciona muy bien, por cierto, es que si tiene excepciones como efectos secundarios, debe plantearlos, en lugar de devolverlos. La biblioteca Mock es buena para permitirle asignar una excepción al side_effect y descifrarlo, pero con este método tiene que hacer bricolaje. – ThatsAMorais

8

He encontré con esto cuando estaba haciendo mi propia prueba. Si no se preocupan por la captura de llamadas a su methodfromdepclass(), pero sólo tiene que devolver algo, a continuación, lo siguiente puede ser suficiente:

def makeFakeMethod(mapping={}): 
    def fakeMethod(inputParam): 
     return mapping[inputParam] if inputParam in mapping else MagicMock() 
    return fakeMethod 

mapping = {42:"Called with 42", 59:"Called with 59"} 
mockobj.methodfromdepclass = makeFakeMethod(mapping) 

Aquí hay una versión con parámetros:

def makeFakeMethod(): 
    def fakeMethod(param): 
     return "Called with " + str(param) 
    return fakeMethod 
+1

+1: sintaxis más ajustada que el patrón if-block con 'side_effect', ¿tal vez devuelva Mock() en lugar de none para que se mantenga el comportamiento predeterminado? –

+0

Gracias por la sugerencia. Se actualizará para devolver MagicMock() en su lugar. – Addison

39

Un poco más dulce :

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}[x] 

o para varios argumentos:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}[x] 

o con un valor por defecto:

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}.get(x, 20000) 

o una combinación de ambos:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}.get(x, 20000) 

y alegremente en las alturas vamos.

+0

Poco cortante, pero me gusta. ¿Qué sucede si el 'lambda' devuelve una instancia' Mock' para que los comportamientos no contados vuelvan al comportamiento normal de la biblioteca 'simulacro'? –

Cuestiones relacionadas