Objeto de simulación de Python con método llamado varias veces


Tengo una clase que estoy probando que tiene como dependencia otra clase (una instancia de la cual se pasa al método init del CORTE). Quiero burlarme de esta clase usando la biblioteca de simulación de Python.

Lo que tengo es algo 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 parametrizado, y como tal quiero crear un único objeto mock donde dependiendo de qué argumentos se pasan a methodfromdepclass devuelve diferentes valores.

El la razón por la que quiero este comportamiento parametrizado es que quiero crear múltiples instancias de ClassUnderTest que contengan diferentes valores (cuyos valores son producidos por lo que se devuelve del mockobj).

Un poco lo que estoy pensando (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 logro este tipo de semántica "ifcalledwith"?

Author: Adam Parkin, 2011-10-05

3 answers

Intenta 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
 65
Author: k.parnell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-05 18:29:20

Un poco más dulce:

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

O para múltiples argumentos:

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

O con un valor predeterminado:

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 lo alto vamos.

 42
Author: abourget,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-29 23:04:56

Me he encontrado con esto cuando estaba haciendo mis propias pruebas. Si no te importa capturar llamadas a tu methodfromdepclass() pero solo lo necesitas para devolver algo, entonces 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 parametrizada:

def makeFakeMethod():
    def fakeMethod(param):
        return "Called with " + str(param)
    return fakeMethod
 8
Author: Addison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-11 22:14:58