当我直接使用 unittest.mock.patch 来修补一个方法时,我可以使用 mock.assert_called_with() 函数来正确地断言该方法是否被某些值所调用。然而,当我直接给一个类打补丁时,我对该类本身或该类的任何方法使用的任何断言都会以 "AssertionError: 尽管类和方法都被调用了,但还是出现了 "AssertionError: Not Called"。
在unittest文档中,有一些成功地给类打补丁和使用断言的例子。unittest mock doc
这是我的测试,直接打上SiteMetaData类方法的补丁,打上EdgeAPIRoutes类的补丁。
边缘APIRoutes类打了补丁:
@patch("repo.routes.EdgeAPIRoutes")
@patch("repo.site_meta_data.SiteMetaData.get_repo_type")
@patch("repo.site_meta_data.SiteMetaData.get_edge_api_inputs")
def test_get_route_list_repo_interface_edge(self, mock_edge_api_inputs,
mock_get_repo_type, mock_edge_api_routes_class):
"""
Test that SiteMetaData.get_route_list is used
appropriately and returns expected values from
mock as a valid response object
"""
from usecase import interface
from usecase import request_objects
from usecase.response_objects import ResponseSuccess
from utils.utils import DatetimeString
all_filters = {"filters" : {"parent_company" : "parent_1",
"site" : "site_1",
"start_date" : DatetimeString("2020-01-01 00:00:00.000"),
"end_date" : DatetimeString("2020-05-01 00:00:00.000")
}
}
example_route_list = [
"route_1",
"route_2",
"route_3"
]
edge_inputs = {
"edge_site" : "edge_site_name",
"edge_key" : "ffff-ffff-ffff"
}
error_response = None
mock_edge_api_inputs.return_value = [
edge_inputs["edge_site"],
edge_inputs["edge_key"],
error_response
]
mock_get_repo_type.return_value = [
"Minestar Edge",
error_response
]
mock_edge_api_routes_class.get_route_list.return_value = [
example_route_list,
error_response
]
resp = interface.get_route_list(request=request_objects.RouteList.request_wfilters(all_filters))
mock_get_repo_type.assert_called_with(all_filters["filters"]["parent_company"],
all_filters["filters"]["site"])
mock_edge_api_inputs.assert_called_with(all_filters["filters"]["parent_company"],
all_filters["filters"]["site"])
mock_edge_api_routes_class.get_route_list.assert_called_with(all_filters["filters"]["start_date"],
all_filters["filters"]["end_date"])
mock_edge_api_routes_class.assert_called_once_with(edge_inputs["edge_site"], edge_inputs["edge_key"])
self.assertTrue(bool(resp))
self.assertEqual(resp.type_, ResponseSuccess.SUCCESS)
self.assertEqual(resp.value["routes"], example_route_list)
方法
def get_route_list(request):
"""
"""
interfacelog.info("running get_route_list")
if bool(request):
repo_type, repo_type_error = site_meta_data.get_repo_type(request.filters["parent_company"],
request.filters["site"])
if repo_type_error is not None:
'''
handle any get repo type system errors
'''
pass
if repo_type == "Minestar Edge":
edge_site, edge_key, edge_error = site_meta_data.get_edge_api_inputs(request.filters["parent_company"],
request.filters["site"])
ear = EdgeAPIRoutes(edge_site, edge_key)
rl, routes_error = ear.get_route_list(request.filters["start_date"],
request.filters["end_date"])
if routes_error is not None:
'''
handle any get routes system error
'''
pass
success_resp = response_objects.ResponseSuccess()
success_resp.value = {"routes" : rl}
return success_resp
else:
'''
handle and errors due to an invalid request
'''
return response_objects.ResponseFailure.build_from_invalid_request_object(request)
结果
SiteMetaData补丁方法的断言通过,而EdgeAPIRoutes类和方法的断言失败。根据文档,似乎这两种打补丁的方式都应该有自己的断言通过,但当我直接用unittest.mock.patch打一个方法时,我却可以使用EdgeAPIRoutes类和方法的断言。
正如@MrBeanBremen在他的评论中指出的那样,被打补丁的类在哪里是关键。在我的问题中,我是在源头打补丁,但由于它们被导入到我的interface.py文件中,所以需要在该位置打补丁。
另外,由于SiteMetaData是在全局层面实例化的,所以需要打补丁的是它的实例而不是类本身。因为,EdgeAPIRoutes是在get_route_list函数中实例化的,所以需要对类本身进行修补。然后,为了给EdgeAPIRoutes的实例方法分配返回值,需要使用被修补类的return_value。
mock_edge_api_routes_class.return_value.get_route_list.return_value = [
example_route_list,
error_response
]
由于site_meta_data实例是直接打补丁的,所以实例方法的值可以直接在补丁中分配。
mock_site_meta_data.get_edge_api_inputs.return_value = [
edge_inputs["edge_site"],
edge_inputs["edge_key"],
error_response
]
接口.py
import logging
from repo.site_meta_data import SiteMetaData
from repo.routes import EdgeAPIRoutes
from usecase import response_objects
interfacelog = logging.getLogger(__name__)
site_meta_data = SiteMetaData()
def get_route_list(request):
"""
"""
interfacelog.info("running get_route_list")
if bool(request):
repo_type, repo_type_error = site_meta_data.get_repo_type(request.filters["parent_company"],
request.filters["site"])
if repo_type_error is not None:
'''
handle any get repo type system errors
'''
pass
if repo_type == "Minestar Edge":
edge_site, edge_key, edge_error = site_meta_data.get_edge_api_inputs(request.filters["parent_company"],
request.filters["site"])
ear = EdgeAPIRoutes(edge_site, edge_key)
rl, routes_error = ear.get_route_list(request.filters["start_date"],
request.filters["end_date"])
if routes_error is not None:
'''
handle any get routes system error
'''
pass
success_resp = response_objects.ResponseSuccess()
success_resp.value = {"routes" : rl}
return success_resp
else:
'''
handle and errors due to an invalid request
'''
return response_objects.ResponseFailure.build_from_invalid_request_object(request)
test_interface函数补丁设置
@patch("usecase.interface.EdgeAPIRoutes")
@patch("usecase.interface.site_meta_data")
def test_get_route_list_repo_interface_edge(self, mock_site_meta_data,
mock_edge_api_routes_class):
"""
Test that SiteMetaData.get_route_list is used
appropriately and returns expected values from
mock as a valid response object
"""
...