Python Requests Mock 没有捕获超时异常

Python Requests Mock doesn't catch Timeout exception

我写了一个单元测试来测试请求包的超时

my_module.py:

import requests

class MyException(Exception): pass

def my_method():
    try:
        r = requests.get(...)
    except requests.exceptions.Timeout:
        raise MyException()

单元测试:

from mock import patch
from unittest import TestCase
from requests.exceptions import Timeout

from my_module import MyException

@patch('my_module.requests')
class MyUnitTest(TestCase):
    def my_test(self, requests):
        def get(*args, **kwargs):
            raise Timeout()

        requests.get = get

        try:
            my_module.my_method(...)
        except MyException:
            return

        self.fail("No Timeout)

但是当它运行时,my_method 中的 try 块永远不会捕获 requests.exceptions.Timeout

patch('my_module.requests') 将用新的模拟对象替换 my_module.requests,但是在您的测试方法中,您替换了直接导入的 requests.get 方法,因此在原始请求模块上,它意味着更改不会反映在您的模块中。

如果在您的测试方法中将其替换为 my_module 中的模拟请求,它应该可以工作:

my_module.requests.get = get

我在这里看到两个问题。一个可以直接解决您的问题,第二个是对 Mocking 框架的轻微误用,进一步简化了您的实施。

首先,为了直接解决您的问题,根据您希望如何测试您的断言,您在这里实际想要做什么:

requests.get = get

应该在这里使用side_effect来帮助提出您的例外。根据 documentation:

side_effect allows you to perform side effects, including raising an exception when a mock is called

考虑到这一点,您真正需要做的就是:

requests.get.side_effect = get

这应该让您提出例外。但是,您可能 可能 遇到此错误:

TypeError: catching classes that do not inherit from BaseException is not allowed

这可能是 最好的 通过实际阅读 很好的答案来解释为什么会发生这种情况。有了这个答案,采纳这个建议实际上只模拟出你 需要 的东西将有助于完全解决你的问题。所以,最后,您的代码实际上看起来像这样,使用模拟的 get 而不是模拟的 requests 模块:

class MyUnitTest(unittest.TestCase):

    @patch('my_module.requests.get')
    def test_my_test(self, m_get):
        def get(*args, **kwargs):
            raise Timeout()

        m_get.side_effect = get

        try:
            my_method()
        except MyException:
            return

实际上,您现在可以通过更好地使用 assertRaises 而不是 try/except 的单元测试来进一步简化此操作。这最终只会断言调用该方法时引发了异常。此外,您不需要 创建一个会引发超时的新方法,您实际上可以简单地声明您的模拟 get 将有一个引发异常的 side_effect。所以你可以用这个简单地替换整个 def get

m_get.side_effect = Timeout()

然而,您实际上可以直接将其放入补丁装饰器中,因此,现在您的最终代码将如下所示:

class MyUnitTest(unittest.TestCase):

    @patch('my_module.requests.get', side_effect=Timeout())
    def test_my_test(self, m_get):    
        with self.assertRaises(MyException):
            my_method()

希望对您有所帮助!