基于输入参数模拟python函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16162015/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 21:55:14  来源:igfitidea点击:

Mocking python function based on input arguments

pythonunit-testingmockingmockito

提问by Juan Antonio Gomez Moriano

We have been using Mockfor python for a while.

我们使用Mockfor python 已经有一段时间了。

Now, we have a situation in which we want to mock a function

现在,我们有一种情况,我们想模拟一个函数

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

Normally, the way to mock this would be (assuming foo being part of an object)

通常,模拟它的方法是(假设 foo 是对象的一部分)

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

Even, if i call foo() a couple of times i can use

甚至,如果我调用 foo() 几次,我可以使用

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

Now, I am facing a situation in which I want to return a fixed value when the input parameter has a particular value. So if let's say "my_param" is equal to "something" then I want to return "my_cool_mock"

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

This seems to be available on mockito for python

这似乎在mockito for python上可用

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

I have been searching on how to achieve the same with Mock with no success?

我一直在寻找如何使用 Mock 实现相同的目标但没有成功?

Any ideas?

有任何想法吗?

采纳答案by Amber

If side_effectis a function then whatever that function returns is what calls to the mock return. The side_effectfunction is called with the same arguments as the mock. This allows you to vary the return value of the call dynamically, based on the input:

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

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

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

回答by Juan Antonio Gomez Moriano

As indicated at Python Mock object with method called multiple times

Python Mock 对象所示,方法多次调用

A solution is to write my own side_effect

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

That does the trick

这就是诀窍

回答by Shubham Chaudhary

Side effect takes a function (which can also be a lambda function), so for simple cases you may use:

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

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

回答by caleb

Just to show another way of doing it:

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

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

回答by Novice

I know its quite an old question, might help as an improvement using python lamdba

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

回答by Tomasz Bartkowiak

You can also use @mock.patch.object:

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

Let's say a module my_module.pyuses pandasto read from a database and we would like to test this module by mocking pd.read_sql_tablemethod (which takes table_nameas argument).

假设一个模块my_module.py用于pandas从数据库中读取,我们想通过模拟pd.read_sql_table方法(table_name作为参数)来测试这个模块。

What you can do is to create (inside your test) a db_mockmethod that returns different objects depending on the argument provided:

您可以做的是创建(在您的测试中)一个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

In your test function you then do:

在您的测试功能中,您可以执行以下操作:

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`

回答by Manu

I've ended up here looking for "how to mock a function based on input arguments" and I finally solved this creating a simple aux function:

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

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

Now:

现在:

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'

Hope this will help someone!

希望这会帮助某人!

回答by Hernan

You can also use partialfrom functoolsif you want to use a function that takes parameters but the function you are mocking does not. E.g. like this:

如果您想使用带参数的函数但您模拟的函数不带参数,您也可以使用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))

This will return a callable that doesn't accept parameters (like Django's timezone.now()), but my mock_year function does.

这将返回一个不接受参数的可调用对象(如 Django 的 timezone.now()),但我的 mock_year 函数接受。

回答by Arseniy Panfilov

If you "want to return a fixed value when the input parameter has a particular value", maybe you don't even need a mock and could use a dictalong with its getmethod:

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

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

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

This works well when your fake's output is a mappingof input. When it's a functionof input I'd suggest using side_effectas per Amber's answer.

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

You can also use a combination of both if you want to preserve Mock's capabilities (assert_called_once, call_countetc):

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

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