如何使python模拟出函数以函数的参数为​​条件返回一个特定的值?

Saq*_*Ali 6 python mocking

我有一个python 2.7x Tornado应用程序,运行时提供一些RESTful api端点.

我的项目文件夹包含许多依赖python mock模块的测试用例,如下所示.

from tornado.testing import AsyncHTTPTestCase
from mock import Mock, patch
import json
from my_project import my_model

class APITestCases(AsyncHTTPTestCase):

    def setUp(self):
        pass

    def tearDown(self):
        pass

    @patch('my_project.my_model.my_method')
    def test_something(
        self,
        mock_my_method
    ):

        response = self.fetch(
            path='http://localhost/my_service/my_endpoint',
            method='POST',
            headers={'Content-Type': 'application/json'},
            body=json.dumps({'hello':'world'})
        )
Run Code Online (Sandbox Code Playgroud)

RESTful端点分别http://localhost/my_service/my_endpoint有两个内部调用my_method:my_method(my_arg=1)my_method(my_arg=2).

我想my_method在这个测试用例中模拟出来,0如果用my_arg== 2 调用它会返回,否则它应该返回它通常会返回的内容.我该怎么做?

我知道我应该这样做:

mock_my_method.return_value = SOMETHING
Run Code Online (Sandbox Code Playgroud)

但是我不知道如何正确地指定某些东西,以便它的行为是以调用my_method的参数为条件的.有人可以给我看看还是给我一个例子?

hoe*_*ing 7

my_method我想在这个测试用例中进行模拟,如果使用 调用它,它会返回 0 my_arg==2,但否则它应该返回通常总是返回的内容。我该怎么做?

编写您自己的方法,根据条件模拟调用原始方法:

from my_project import my_model

my_method_orig = my_project.my_model.my_method
def my_method_mocked(self, *args, my_arg=1, **kwargs):
    if my_arg == 2:  # fake call
        return 0
    # otherwise, dispatch to real method
    return my_method_orig(self, *args, **kwargs, my_arg=my_arg)
Run Code Online (Sandbox Code Playgroud)

对于修补:如果您不需要断言模拟方法的调用频率以及参数等,那么通过new参数传递模拟就足够了:

@patch('my_project.my_model.my_method', new=my_method_mocked)
def test_something(
    self,
    mock_my_method
):

    response = self.fetch(...)
    # this will not work here:
    mock_my_method.assert_called_with(2)
Run Code Online (Sandbox Code Playgroud)

如果您想调用整个模拟断言机制,请side_effect按照其他答案中的建议使用。例子:

@patch('my_project.my_model.my_method', side_effect=my_method_mocked, autospec=True)
def test_something(
    self,
    mock_my_method
):

    response = self.fetch(...)
    # mock is assertable here 
    mock_my_method.assert_called_with(2)
Run Code Online (Sandbox Code Playgroud)


geo*_*xsh 6

您可以使用side_effect动态更改返回值:

class C:
    def foo(self):
        pass  

def drive():
    o = C()
    print(o.foo(my_arg=1))
    print(o.foo(my_arg=2))  

def mocked_foo(*args, **kwargs):
    if kwargs.get('my_arg') == 2:
        return 0
    else:
        return 1

@patch('__main__.C.foo')
def test(mock):
    mock.side_effect = mocked_foo
    drive()
Run Code Online (Sandbox Code Playgroud)

更新:当你想my_method在某种情况下运行原始代码时,你可能需要一个方法代理,Mock无法取回正在修补的真实函数对象。

from unittest.mock import patch

class MyClass:

    def my_method(self, my_arg):
        return 10000

def func_wrapper(func):
    def wrapped(*args, **kwargs):
        my_arg = kwargs.get('my_arg')
        if my_arg == 2:
            return 0
        return func(*args, **kwargs)
    return wrapped

def drive(o, my_arg):
    print('my_arg', my_arg, 'ret', o.my_method(my_arg=my_arg))

def test():
    with patch.object(MyClass, 'my_method', new=func_wrapper(MyClass.my_method)):
        o = MyClass()
        drive(o, 1)
        drive(o, 2)
Run Code Online (Sandbox Code Playgroud)

将输出:

my_arg 1 ret 10000
my_arg 2 ret 0
Run Code Online (Sandbox Code Playgroud)