Mocking python function based on input arguments
我们一直在使用Mock for python。
现在,我们有一种情况,我们想要模拟一个函数
1 2 3 | def foo(self, my_param): #do something here, assign something to my_result return my_result |
通常,模拟这个的方法是(假设foo是对象的一部分)
1 | self.foo = MagicMock(return_value="mocked!") |
即便如此,如果我多次调用foo(),我也可以使用
1 | self.foo = MagicMock(side_effect=["mocked once","mocked twice!"]) |
现在,我面临的情况是,当输入参数具有特定值时,我想返回固定值。 所以如果让我们说"my_param"等于"某事"那么我想要返回"my_cool_mock"
这似乎可以在mock上用于python
1 | when(dummy).foo("something").thenReturn("my_cool_mock") |
我一直在寻找如何与Mock达成同样的目标并没有成功?
有任何想法吗?
If
side_effect is a function then whatever that function returns is
what calls to the mock return. Theside_effect function 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:
1
2
3
4
5
6
7
8
9
10 >>> 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
正如Python Mock对象所指出的那样,多次调用方法
解决方案是编写自己的side_effect
1 2 3 4 5 6 7 8 9 | 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 |
这就是诀窍
副作用需要一个函数(也可以是lambda函数),因此对于简单的情况,您可以使用:
1 | m = MagicMock(side_effect=(lambda x: x+1)) |
我最终在这里寻找"如何根据输入参数模拟一个函数",我终于解决了这个问题,创建了一个简单的辅助函数:
1 2 | def mock_responses(responses, default_response=None): return lambda input: responses[input] if input in responses else default_response |
现在:
1 2 3 4 5 6 7 8 9 10 11 | 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' |
希望这会对某人有所帮助!
只是为了表明另一种方式:
1 2 3 4 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 |
您也可以使用
假设一个模块
你可以做的是创建一个
1 2 3 4 5 | def db_mock(**kwargs): if kwargs['table_name'] == 'table_1': # return some DataFrame elif kwargs['table_name'] == 'table_2': # return some other DataFrame |
在您的测试功能中,您可以:
1 2 3 4 5 6 7 8 | 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` |
我知道这是一个很老的问题,可能有助于使用python lamdba进行改进
1 2 3 4 5 6 | 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 |