Смысл функции python, основанный на входных аргументах

Мы использовали Mock для python какое-то время.

Теперь у нас есть ситуация, когда мы хотим издеваться над функцией

def foo(self, my_param):
    #do something here, assign something to my_result
    return my_result

Как правило, способ издеваться над этим был бы (предполагая, что foo является частью объекта)

self.foo = MagicMock(return_value="mocked!")

Даже если я вызываю foo() пару раз, я могу использовать

self.foo = MagicMock(side_effect=["mocked once", "mocked twice!"])

Теперь я сталкиваюсь с ситуацией, в которой я хочу вернуть фиксированное значение, когда входной параметр имеет определенное значение. Так что если пусть say "my_param" равен "что-то", тогда я хочу вернуть "my_cool_mock"

Это похоже на mockito для python

when(dummy).foo("something").thenReturn("my_cool_mock")

Я искал, как добиться успеха с Mock без успеха?

Любые идеи?

Ответ 1

Если side_effect - это функция, то любая возвращаемая функция что вызывает возврат к макету. Функция side_effect вызывается с те же аргументы, что и макет. Это позволяет варьировать возврат значение динамического вызова на основе ввода:

>>> def side_effect(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

http://www.voidspace.org.uk/python/mock/mock.html#calling

Ответ 2

Как указано в Pockon Mock объект с методом, называемым несколько раз

Решение состоит в том, чтобы написать свой собственный 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

Это трюк

Ответ 3

Побочный эффект принимает функцию (которая также может быть лямбда-функцией), поэтому для простых случаев вы можете использовать:

m = MagicMock(side_effect=(lambda x: x+1))

Ответ 4

Я закончил здесь поиском "как смоделировать функцию на основе входных аргументов" и наконец решил эту проблему, создав простую вспомогательную функцию:

def mock_responses(responses, default_response=None):
  return lambda input: responses[input] if input in responses else default_response

Сейчас:

my_mock.foo.side_effect = mock_responses({'x': 42, 'y': [1,2,3]})
my_mock.goo.side_effect = mock_responses({'hello': 'world'}, 
                                         default_response='hi')
...

my_mock.foo('x') # => 42
my_mock.foo('y') # => [1,2,3]
my_mock.foo('unknown') # => None

my_mock.goo('hello') # => 'world'
my_mock.goo('ey') # => 'hi'

Надеюсь, это кому-нибудь поможет!

Ответ 5

Просто, чтобы показать другой способ сделать это:

def mock_isdir(path):
    return path in ['/var/log', '/var/log/apache2', '/var/log/tomcat']

with mock.patch('os.path.isdir') as os_path_isdir:
    os_path_isdir.side_effect = mock_isdir

Ответ 6

Вы также можете использовать @mock.patch.object:

Допустим, модуль my_module.py использует pandas для чтения из базы данных, и мы хотели бы протестировать этот модуль с pd.read_sql_table метода 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'

Ответ 7

Вы также можете использовать partial из functools, если вы хотите использовать функцию, которая принимает параметры, а функция, над которой вы работаете, - нет. Например. как это:

def mock_year(year):
    return datetime.datetime(year, 11, 28, tzinfo=timezone.utc)
@patch('django.utils.timezone.now', side_effect=partial(mock_year, year=2020))

Это вернет вызываемый объект, который не принимает параметры (как Django timezone.now()), но моя функция mock_year принимает.

Ответ 8

Я знаю, это довольно старый вопрос, может помочь как улучшение, используя Python Lamdba

self.some_service.foo.side_effect = lambda *args:"Called with 42" \
            if args[0] == 42 \
            else "Called with 42" if args[0] == 43 \
            else "Called with 43" if args[0] == 43 \
            else "Called with 45" if args[0] == 45 \
            else "Called with 49" if args[0] == 49 else None