我们在python中使用Mock已经有一段时间了。

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

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等于something那么我要返回my_cool_mock

这似乎在python的mockito上可用

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

我一直在寻找如何实现同样的Mock没有成功?

什么好主意吗?


当前回答

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

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

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

当fake的输出是输入的映射时,这种方法很有效。当它是输入的函数时,我建议根据Amber的回答使用side_effect。

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

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

其他回答

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

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

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

当fake的输出是输入的映射时,这种方法很有效。当它是输入的函数时,我建议根据Amber的回答使用side_effect。

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

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

如果你想使用一个带参数的函数,而你要模拟的函数不带参数,你也可以使用来自functools的partial。例如:

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函数可以。

Side effect接受一个函数(也可以是lambda函数),所以对于简单的情况,你可以使用:

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

如在多次调用方法的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

这就成功了

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

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'

希望这能帮助到一些人!