215

我们使用Mock for 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 实现同样的目标,但没有成功?

有任何想法吗?

4

9 回答 9

262

如果side_effect_func是一个函数,那么该函数返回的就是对模拟返回的调用。该side_effect_func函数使用与模拟相同的参数调用。这允许您根据输入动态改变调用的返回值:

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

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

于 2013-04-23T06:05:45.360 回答
76

Python 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 kwargs['foo'] == 7:
        return "Foo is seven"

mockobj.mockmethod.side_effect = my_side_effect

这就是诀窍

于 2013-04-23T06:19:43.397 回答
23

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

m = MagicMock(side_effect=(lambda x: x+1))
于 2016-08-30T05:19:38.323 回答
12

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

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

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

当你的假输出是输入的映射时,这很有效。当它是输入的函数时,我建议side_effect按照Amber的回答使用。

如果您想保留Mock的功能(等)assert_called_once,也可以将两者结合使用:call_count

self.mock.side_effect = {'input1': 'value1', 'input2': 'value2'}.get
于 2020-05-19T03:20:13.883 回答
7

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

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'

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

于 2019-07-24T08:45:35.237 回答
6

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

我写了一个轻量级的Mock(叫做NextMock)来增强内置的mock来解决这个问题,下面是一个简单的例子:

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

它还支持参数匹配器:

from nextmock import Arg, Mock

m = Mock()

m.with_args(1, 2, Arg.Any).returns(123)

assert m(1, 2, 1) == 123
assert m(1, 2, "123") == 123

希望这个包可以使测试更愉快。随时提供任何反馈。

于 2021-01-03T08:59:53.000 回答
5

如果你想使用一个带参数的函数,但你要模拟的函数没有,你也可以使用partialfrom 。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 函数可以。

于 2019-09-02T07:38:25.860 回答
2

您还可以使用@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`
于 2019-06-13T19:40:50.860 回答
2

只是为了展示另一种方式:

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
于 2016-12-08T21:08:13.917 回答