从类方法引发的模拟异常具有副作用,给出“未引发”

时间:2019-11-14 19:06:17

标签: python-3.x exception pytest pytest-mock

注意:此问题基于previous question I asked,但根据this answer 修改为不同。

我使用side_effect,试图在调用模拟程序时引发'URLError'异常,但遇到一个我不理解的DID NOT RAISE错误。

我有一个带有类方法Query的{​​{1}}类,这可能引发几个异常。我make_request_and_get_response的{​​{1}}方法内捕获'URLError'异常,所以我应该应该会被引发并随后对其异常进行了模拟。

query.py

get_response_from_external_api

main.py

main.py

我正在使用from urllib.request import urlopen import contextlib import urllib class Query: def __init__(self, a, b): self.a = a self.b = b self.query = self.make_query() def make_query(self): # create query request using self.a and self.b return query def make_request_and_get_response(self): # <--- the 'dangerous' method that can raise exceptions with contextlib.closing(urlopen(self.query)) as response: return response.read().decode('utf-8') 来模拟该“危险”方法(from foo.query import * def get_response_from_external_api(query): try: response = query.make_request_and_get_response() except urllib.error.HTTPError as e: print('Got a HTTPError: ', e) except Exception: print('Got a generic Exception!') # handle this exception if __name__ == "__main__": query = Query('input A', 'input B') result = get_response_from_external_api(query) return result ),并带有针对特定异常的副作用。然后,我将继续创建一个模拟的pytest对象,以在调用make_request_and_get_response时使用,期望此最后一次调用会产生'URLError'异常。

test_main.py

Query

上面的测试给出以下错误:

make_request_and_get_response

即使我捕获了import pytest from unittest.mock import patch from foo.query import Query from foo.main import get_response_from_external_api class TestExternalApiCall: @patch('foo.query.Query') def test_url_error(self, mockedQuery): with patch('foo.query.Query.make_request_and_get_response', side_effect=Exception('URLError')): with pytest.raises(Exception) as excinfo: q= mockedQuery() foo.main.get_response_from_external_api(q) assert excinfo.value = 'URLError' # assert excinfo.value.message == 'URLError' # this gives object has no attribute 'message' 异常,然后在> foo.main.get_response_from_external_api(q) E Failed: DID NOT RAISE <class 'Exception'> id='72517784'>") == 'URLError' 中重新引发它,也会发生相同的错误。

有人可以帮助我理解我所缺少的内容,以便能够在pytest中引发异常吗?


根据@SimeonVisser的回复

更新

如果我修改'URLError'以删除get_response_from_external_api情况:

main.py

然后在except Excpetion中进行测试:

def get_response_from_external_api(query):
    try:
        response = query.make_request_and_get_response()
    except urllib.error.URLError as e:
        print('Got a URLError: ', e)
    except urllib.error.HTTPError as e:
        print('Got a HTTPError: ', e)

测试通过了。

1 个答案:

答案 0 :(得分:1)

问题在于get_response_from_external_api()已经捕获了Exception并且没有将其引发到该函数之外的任何地方。因此,当您模拟查询并使make_request_and_get_response引发异常时,pytest将不会看到该异常,因为get_response_from_external_api()已经捕获了它。

如果将其更改为以下内容,则pytest有机会看到它:

    except Exception:
        print('Got a generic Exception!')
        # handle this exception
        raise

测试用例似乎也无法按预期工作。可以简化为以下内容(直接创建模拟查询并传递该查询要容易一些):

import pytest
from unittest import mock
from foo.main import get_response_from_external_api


class TestExternalApiCall:
    def test_url_error(self):
        mock_query = mock.Mock()
        mock_query.make_request_and_get_response.side_effect = Exception('URLError')
        with pytest.raises(Exception) as excinfo:
            get_response_from_external_api(mock_query)
        assert str(excinfo.value) == 'URLError'

然后测试用例通过(除了也用raise进行了上述更改)。


回答问题1:

区别在于,您正在使用装饰器来模拟Query,但是随后在测试用例中模拟了类foo.query.Query以引发该异常。但是mockedQuery丝毫没有改变,实际上对该方法所做的任何更改。因此qmock.Mock()的常规实例,没有任何特殊之处。

您可以将其更改为以下内容(类似于上述方法):

import pytest
from unittest.mock import patch
from foo.query import Query
from foo.main import get_response_from_external_api


class TestExternalApiCall:
    @patch('foo.query.Query')
    def test_url_error(self, mockedQuery):
        with patch.object(mockedQuery, 'make_request_and_get_response', side_effect=Exception('URLError')):
            with pytest.raises(Exception) as excinfo:
                get_response_from_external_api(mockedQuery)
            assert str(excinfo.value) == 'URLError'

如果您的with patch('foo.query.Query........语句在代码中的任何地方都可以工作,然后从该位置实例化一个Query实例。

回答问题2:

嗯,我无法重现-您在本地拥有的测试用例与问题中的相同吗?由于foo.main.get_response_from_external_api(q)不存在(foo未导入),我不得不对其进行了修改,因此我将其更改为调用get_response_from_external_api(q),并且不断得到:

>               get_response_from_external_api(q)
E               Failed: DID NOT RAISE <class 'Exception'>