web-dev-qa-db-fra.com

Python Mock objet avec méthode appelée plusieurs fois

J'ai une classe que je teste qui a comme dépendance une autre classe (dont une instance est passée à la méthode init de la CUT). Je veux simuler cette classe en utilisant la bibliothèque Python Mock.

Ce que j'ai est quelque chose comme:

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)

Ce qui est bien, mais "methodfromdepclass" est une méthode paramétrée, et en tant que tel, je veux créer un seul objet fantaisie où, selon les arguments passés à methodfromdepclass, il renvoie des valeurs différentes.

La raison pour laquelle je veux ce comportement paramétré est que je veux créer plusieurs instances de ClassUnderTest qui contiennent des valeurs différentes (dont les valeurs sont produites par ce qui est renvoyé par le mockobj).

Un peu ce que je pense (cela ne fonctionne bien sûr pas):

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

Comment réaliser ce type de sémantique "ifcalledwith"?

47
Adam Parkin

Essayez 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
74
k.parnell

Un peu plus doux:

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

ou pour plusieurs arguments:

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

ou avec une valeur par défaut:

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

ou une combinaison des deux:

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

et joyeusement en haut nous allons.

48
abourget

Je suis tombé sur cela lorsque je faisais mes propres tests. Si vous ne vous souciez pas de capturer des appels à votre methodfromdepclass () mais que vous en avez juste besoin pour retourner quelque chose, alors ce qui suit peut suffire:

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)

Voici une version paramétrée:

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

Comme dans ici , en plus d'utiliser side_effect in nittest.mock.Mock vous pouvez également utiliser @mock.patch.object avec new_callable, qui vous permet de patcher un attribut d'un objet avec un objet factice.

Disons qu'un module my_module.py utilise pandas pour lire dans une base de données et nous aimerions tester ce module en se moquant de pd.read_sql_table méthode (qui prend table_name comme argument).

Ce que vous pouvez faire est de créer (dans votre test) un db_mock méthode qui renvoie différents objets en fonction de l'argument fourni:

def db_mock(**kwargs):
    if kwargs['table_name'] == 'table_1':
        # return some DataFrame
    Elif kwargs['table_name'] == 'table_2':
        # return some other DataFrame

Dans votre fonction de test, vous effectuez ensuite:

import my_module as my_module_imported

@mock.patch.object(my_module_imported.pd, "read_sql_table", new_callable=lambda: db_mock)
def test_my_module(mock_read_sql_table):
    # You can now test any methods from `my_module`, e.g. `foo` and any call this 
    # method does to `read_sql_table` will be mocked by `db_mock`, e.g.
    ret = my_module_imported.foo(table_name='table_1')
    # `ret` is some DataFrame returned by `db_mock`
0
Tomasz Bartkowiak