从具有副作用的 class 方法引发的模拟异常给出 'did not raise'
Mock exception raised from class method with side effect gives 'did not raise'
注意:此问题基于 but modified to be different according to 。
使用 side_effect,我试图在调用模拟时引发 'URLError'
异常,但我收到一个我不理解的 DID NOT RAISE
错误。
我有一个 Query
class 和一个 class 方法 make_request_and_get_response
可以引发几个异常。我不是在main.py
的get_response_from_external_api
方法中捕获'URLError'
异常,所以我应该能够期望被引发,随后它的异常被嘲笑。
query.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')
main.py
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
,我试图模拟 'dangerous' 方法 (make_request_and_get_response
) 对特定异常有副作用。然后,我继续创建一个模拟的 Query
对象以在调用 make_request_and_get_response
时使用,期望最后一次调用给出 'URLError' 异常。
test_main.py
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'
即使我捕捉到 'URLError'
异常然后在 get_response_from_external_api
中重新引发它,也会发生同样的错误。
有人可以帮助我理解我缺少什么以便能够在 pytest 中引发异常吗?
根据@SimeonVisser 的回复更新:
如果我修改 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)
然后test_main.py
中的测试:
def test_url_error2(self):
mock_query = 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'
测试通过 OK。
问题是 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
,然后模拟测试用例中的 class foo.query.Query
以引发该异常。但是 mockedQuery
实际上并没有改变以对该方法做任何不同的事情。所以 q
是 mock.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'
如果代码中的任何地方随后从该位置实例化一个 Query
实例,您的 with patch('foo.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'>
注意:此问题基于
使用 side_effect,我试图在调用模拟时引发 'URLError'
异常,但我收到一个我不理解的 DID NOT RAISE
错误。
我有一个 Query
class 和一个 class 方法 make_request_and_get_response
可以引发几个异常。我不是在main.py
的get_response_from_external_api
方法中捕获'URLError'
异常,所以我应该能够期望被引发,随后它的异常被嘲笑。
query.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')
main.py
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
,我试图模拟 'dangerous' 方法 (make_request_and_get_response
) 对特定异常有副作用。然后,我继续创建一个模拟的 Query
对象以在调用 make_request_and_get_response
时使用,期望最后一次调用给出 'URLError' 异常。
test_main.py
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'
即使我捕捉到 'URLError'
异常然后在 get_response_from_external_api
中重新引发它,也会发生同样的错误。
有人可以帮助我理解我缺少什么以便能够在 pytest 中引发异常吗?
根据@SimeonVisser 的回复更新:
如果我修改 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)
然后test_main.py
中的测试:
def test_url_error2(self):
mock_query = 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'
测试通过 OK。
问题是 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
,然后模拟测试用例中的 class foo.query.Query
以引发该异常。但是 mockedQuery
实际上并没有改变以对该方法做任何不同的事情。所以 q
是 mock.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'
如果代码中的任何地方随后从该位置实例化一个 Query
实例,您的 with patch('foo.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'>