基于输入参数模拟python函数

时间:2013-04-23 05:59:11

标签: python unit-testing mocking mockito

我们已经将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!"])

现在,我面临的情况是,当输入参数具有特定值时,我想返回固定值。所以如果让我们说“my_param”等于“某事”那么我想要返回“my_cool_mock”

这似乎可以在mockito for python

上找到
when(dummy).foo("something").thenReturn("my_cool_mock")

我一直在寻找如何与Mock取得同样成功但没有成功?

有什么想法吗?

10 个答案:

答案 0 :(得分:141)

  

如果side_effect是一个函数,那么该函数返回的是什么   什么叫做模拟回报。调用side_effect函数   与mock相同的参数。这允许您改变回报   动态调用的值,基于输入:

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

答案 1 :(得分:26)

Python Mock object with method called multiple times

所示

解决方案是编写自己的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

这就是诀窍

答案 2 :(得分:8)

副作用需要一个函数(也可以是 lambda函数),因此对于简单的情况,您可以使用:

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

答案 3 :(得分:4)

虽然int32可以达到目的,但是为每个测试用例设置side_effect函数并不是那么方便。

我编写了一个轻量级的 Mock(称为 NextMock)来增强内置模拟来解决这个问题,这里是一个简单的例子:

side_effect

它还支持参数匹配器:

from nextmock import Mock

m = Mock()

m.with_args(1, 2, 3).returns(123)

assert m(1, 2, 3) == 123
assert m(3, 2, 1) != 123

希望这个包能让测试更愉快。随时提供任何反馈。

答案 4 :(得分:3)

如果您“要在输入参数具有特定值时返回固定值” ,也许您甚至不需要模拟,可以使用dict及其get方法:

foo = {'input1': 'value1', 'input2': 'value2'}.get

foo('input1')  # value1
foo('input2')  # value2

当您的假货输出是输入的映射时,这很好用。当它是输入的功能时,我建议根据琥珀色的答案使用side_effect

如果要保留Mock的功能(assert_called_oncecall_count等),也可以将两者结合使用:

self.mock.side_effect = {'input1': 'value1', 'input2': 'value2'}.get

答案 5 :(得分:1)

我最终在这里寻找“ 如何基于输入参数模拟函数”,最后解决了这个问题,创建了一个简单的辅助函数:

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'

希望这会对某人有所帮助!

答案 6 :(得分:0)

只是为了表明另一种方式:

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

答案 7 :(得分:0)

您也可以使用@mock.patch.object

假设模块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`

答案 8 :(得分:0)

如果您要使用带有参数但不模拟的函数,也可以使用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()),但是我的ock_year函数可以接受。

答案 9 :(得分:-2)

我知道这是一个很老的问题,可能会有助于使用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