Python Mock объект с методом, вызываемым несколько раз - PullRequest
46 голосов
/ 05 октября 2011

У меня есть класс, который я тестирую, который имеет в качестве зависимости другой класс (экземпляр которого передается методу init CUT). Я хочу макетировать этот класс с помощью библиотеки Python Mock.

Что у меня есть что-то вроде:

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)

Это нормально, но «methodfromdepclass» является параметризованным методом, и поэтому я хочу создать один фиктивный объект, в котором в зависимости от того, какие аргументы передаются в methodfromdepclass, он возвращает разные значения.

Причина, по которой я хочу, чтобы это параметризованное поведение состояло в том, что я хочу создать несколько экземпляров ClassUnderTest, которые содержат разные значения (значения которых создаются тем, что возвращается из mockobj).

Что-то, о чем я думаю (это, конечно, не работает):

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

Как мне добиться такой семантики "ifcalledwith"?

Ответы [ 4 ]

72 голосов
/ 05 октября 2011

Попробуйте 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
47 голосов
/ 30 ноября 2012

Немного слаще:

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

или для нескольких аргументов:

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

или со значением по умолчанию:

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

или комбинацией обоих:

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

и мы весело поднимаемся наверх.

10 голосов
/ 03 июня 2012

Я столкнулся с этим, когда проводил собственное тестирование.Если вам не нужно захватывать вызовы вашего метода methodderomclass (), а просто нужно, чтобы он что-то возвращал, тогда может быть достаточно следующего:

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)

Вот параметризованная версия:

def makeFakeMethod():
    def fakeMethod(param):
        return "Called with " + str(param)
    return fakeMethod
0 голосов
/ 14 июня 2019

Как и в здесь , кроме использования side_effect в unittest.mock.Mock вы также можете использовать @mock.patch.object с new_callable, что позволяет связать атрибут объекта с фиктивным объектом.

Скажем, модуль my_module.py использует pandas для чтения из базы данных, и мы хотели бы протестировать этот модуль с помощью метода pd.read_sql_table (который принимает table_name в качестве аргумента).

Что вы можете сделать, это создать (внутри вашего теста) метод db_mock, который возвращает различные объекты в зависимости от предоставленного аргумента:

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

В своей тестовой функции вы затем делаете:

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`
...