Python函数不使用模拟对象

时间:2017-09-28 08:52:23

标签: python unit-testing mocking

来自PHP背景我在编写Python单元测试时遇到了以下问题:

我有一个函数foo,它使用Client对象来获取其他API的响应:

from xxxx import Client
def foo (some_id, token):
    path = 'some/api/path'
    with Client.get_client_auth(token) as client:
        response = client.get(path,params).json()
        results = list(response.keys())
    .............

为此,我在另一个python文件中创建了以下单元测试。

from yyyy import foo
class SomethingTestCase(param1, param2):
    def test_foo(self):
        response = [1,2,3]
        with patch('xxxx.Client') as MockClient:
            instance = MockClient.return_value
            instance.get.return_value = response
        result = foo(1,self.token)
        self.assertEqual(response,result)

我不明白为什么foo没有使用模拟的[1,2,3]列表,而是尝试连接到实际的API路径以提取真实数据。

我错过了什么?

2 个答案:

答案 0 :(得分:5)

你做错了3件事:

  • 您正在修补错误的位置。您需要修补yyyy.Client全局,因为这是您导入该名称的方式。

  • 被测代码未调用Client(),它使用不同的方法,因此调用路径不同。

  • 您在补丁生存期之外调用了测试代码。在with块中调用您的代码。

让我们详细介绍一下:

使用from xxxx import Client时,将Client模块全局变量中的新引用yyyy绑定到该对象。您想要替换该引用,而不是xxxx.Client。毕竟,被测代码在其自己的模块中访问Client作为全局。请参阅unittest.mock文档的Where to patch section

您并非在代码中调用 Client。您正在使用类方法(.get_client_auth())。然后,您还将返回值用作上下文管理器,因此分配给client的是上下文管理器上__enter__方法的返回值:

with Client.get_client_auth(token) as client:

您需要模拟该方法链:

with patch('yyyy.Client') as MockClient:
    context_manager = MockClient.get_client_auth.return_value
    mock_client = context_manager.__enter__.return_value
    mock_client.get.return_value = response
    result = foo(1,self.token)

您需要在with中调用下的代码,因为只有在该块中才能修补代码。 with语句使用patch(...)结果作为上下文管理器。输入块后,补丁实际应用于模块,当块退出时,补丁将再次被删除。

最后但并非最不重要的是,在尝试调试此类情况时,您可以打印出Mock.mock_calls attribute;这应该告诉你在对象上实际调用了什么。没有打电话?然后你还没有修补正确的位置,忘记启动补丁,或者在补丁不再存在的时候调用了测试代码。

但是,如果您的补丁正确应用,那么MockClient.mock_calls将类似于:

[call.get_client_auth('token'),
 call.get_client_auth().__enter__(),
 call.get_client_auth().__enter__().get('some/api/path', {'param': 'value'}),
 call.get_client_auth().__exit__(None, None, None)]

答案 1 :(得分:1)

您需要在要使用它的文件中修补Client对象,而不是在其源文件中。到那时,您的测试代码已经运行,客户端对象已经被导入到您要访问API的文件中。

# views.py
from xxxx import Client

# test_file.py
...
with patch('views.Client') as MockClient: # and not 'xxxx.Client'
...

此外,由于您要修补上下文管理器,因此需要提供存根。