来自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路径以提取真实数据。
我错过了什么?
答案 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'
...
此外,由于您要修补上下文管理器,因此需要提供存根。