首页 > 解决方案 > 为什么在使用 unittest 修补类时会出现断言错误?

问题描述

当我直接使用 unittest.mock.patch 修补方法时,我可以使用 mock.assert_call_with() 函数来正确断言该方法是否是使用某些值调用的。但是,当我直接修补类时,即使调用了类和方法,我在类本​​身或任何类方法上使用的任何断言都会返回“AssertionError:未调用”。

unittest 文档中有成功修补类和使用断言的示例:unittest mock doc

这是我使用直接修补 SiteMetaData 类方法和修补 EdgeAPIRoutes 类的测试:

测试

    @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 类和方法的断言失败。根据文档,似乎两种修补方法都应该通过它们的断言。

标签: pythonpython-3.xpython-unittest

解决方案


正如@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
    """
    ...

推荐阅读