Assume this sample code:
def test_foo():
dict = load_dict()
try:
value = dict[foo][bar]
except KeyError:
print('missing foo or bar')
If it raises KeyError because either foo or bar doesn't exist, the test will not fail because the exception is captured. If I add a raise SystemExit(1), it fails, prints the message and shows all the traceback.
My question is, how can I tell pytest that if a KeyError occurred it means the test failed, so that I don't need to raise a SystemExit?
There is a function pytest.fail that explicitly fails the test:
import pytest
def test_foo():
d1 = {'foo': 'bar'}
try:
value = d1['baz']
except KeyError as err:
pytest.fail('this was unexpected: {}'.format(err))
However, the idiomatic way would be using the pytest.raises context manager that verifies the exception is raised, capturing it for analysis with some convenient utilities:
import pytest
def test_foo():
d1 = {'foo': 'bar'}
with pytest.raises(KeyError) as excinfo:
value = d1['baz']
assert excinfo.type == KeyError
assert excinfo.match('baz')
Check out the docs for more examples. If you are familiar with unittest, pytest.raises is the pendant to unittest.TestCase.assertRaises, while pytest.fail is the pendant to unittest.TestCase.fail.
You can use the with pytest.raises constructor:
def test_connection_fails(self,):
with pytest.raises(KeyError) as excinfo:
buckets = list_all_buckets()
Then you can raise an error without using sys.exit
Related
I'd like to test that an exception is raised from another exception type.
import pytest
def throw_with_cause():
raise Exception("Failed") from ValueError("That was unexpected.")
with pytest.raises(Exception): # from ValueError???
throw_with_cause()
I'm surprised not to see a way to inspect the exception chain in the pytest raises docs. https://docs.pytest.org/en/6.2.x/reference.html#pytest-raises
Is there a clean way to do this using ptyest raises?
Until something more readable comes along, I'm doing the following.
import pytest
def throw_with_cause():
raise Exception("Failed") from ValueError("That was unexpected.")
def test_throws_with_cause():
with pytest.raises(Exception, match="Failed") as exc_info:
throw_with_cause()
assert type(exc_info.value.__cause__) is ValueError
From what I understand in test_main_version_exception unit test, the compare_version.version_match() function would raise an exception and ideally this exception should get caught by the except block. But for some reason, the flow is not going into except block.
(1st one seems to be working fine)
Could someone please help me with this? Any suggestions to improve theses tests are also welcome.
main.py file
try:
is_version_same = compare_version.version_match()
if is_version_same:
LOGGER.info("Upgrade not required")
sys.exit()
except compare_version.VersionError as error: #custom error
#### these two statements are not getting covered when I run test_main_version_exception(mocker):
LOGGER.exception("Exception occurred in compare_version: %s", error)
sys.exit(f"Exception occurred in compare_version: {error}")
UNIT TESTS:
def test_main_same_version(mocker):
mocker.patch(
"compare_version.version_match",
return_value=True,
)
with pytest.raises(SystemExit) as ex:
main.main()
assert ex.type == SystemExit
def test_main_version_exception(mocker):
mocker.patch(
"compare_version.version_match",
side_effect=VersionError,
)
with pytest.raises(VersionError) as ex:
main.main()
assert ex.type == VersionError
You have to patch the compare_version used in main.py specifically via mocker.patch("main.compare_version.version_match", ...) regardless if it was:
Imported into main.py:
from some_file import compare_version
Or defined within main.py:
class compare_version: # Sample implementation only as you haven't included this in the question
version_match = lambda: None
VersionError = Exception
As documented:
a.py
-> Defines SomeClass
b.py
-> from a import SomeClass
-> some_function instantiates SomeClass
Now we want to test some_function but we want to mock out SomeClass using patch()... If we use patch() to mock out a.SomeClass then it will have no effect on our test; module b already has a reference to the real SomeClass and it looks like our patching had no effect.
The key is to patch out SomeClass where it is used (or where it is looked up). In this case some_function will actually look up SomeClass in module b, where we have imported it. The patching should look like:
#patch('b.SomeClass')
Also, your assertion with pytest.raises(VersionError) as ex: is incorrect because your implementation doesn't raise VersionError anymore, instead it catches that error and raises SystemExit too. So what you need to assert here is if it raises SystemExit. If what you really need is the VersionError, then replace this line:
sys.exit(f"Exception occurred in compare_version: {error}")
To:
raise # Will re-raise the previous exception, which is the VersionError
Sample run you might want to use as reference.
main.py
import sys
# # Whether it is defined in another file
# from some_file import compare_version
# # Or it is defined here
class compare_version:
version_match = lambda: None
VersionError = ValueError
def main():
try:
is_version_same = compare_version.version_match()
if is_version_same:
print("Upgrade not required")
sys.exit()
except compare_version.VersionError as error: #custom error
print("Exception occurred in compare_version: %s", error)
# If you want this block to raise SystemExit
sys.exit(f"Exception occurred in compare_version: {error}")
# If you want this block to re-raise VersionError
# raise
test_main.py
import pytest
import main
def test_main_same_version(mocker):
mocker.patch(
"main.compare_version.version_match",
return_value=True,
)
with pytest.raises(SystemExit) as ex:
main.main()
assert ex.type == SystemExit
def test_main_version_exception(mocker):
mocker.patch(
"main.compare_version.version_match",
side_effect=main.compare_version.VersionError,
)
with pytest.raises(SystemExit) as ex:
main.main()
assert ex.type == SystemExit
Output
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
_________________________________________________________________________________________ test_main_same_version __________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Upgrade not required
_______________________________________________________________________________________ test_main_version_exception _______________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Exception occurred in compare_version: %s
2 passed in 0.04s
I'm using pytest in a project with a good many custom exceptions.
pytest provides a handy syntax for checking that an exception has been raised, however, I'm not aware of one that asserts that the correct exception message has been raised.
Say I had a CustomException that prints "boo!", how could I assert that "boo!" has indeed been printed and not, say, "<unprintable CustomException object>"?
#errors.py
class CustomException(Exception):
def __str__(self): return "ouch!"
#test.py
import pytest, myModule
def test_custom_error(): # SHOULD FAIL
with pytest.raises(myModule.CustomException):
raise myModule.CustomException == "boo!"
I think what you're looking for is:
def failer():
raise myModule.CustomException()
def test_failer():
with pytest.raises(myModule.CustomException) as excinfo:
failer()
assert str(excinfo.value) == "boo!"
You can use match keyword in raises. Try something like
with pytest.raises(
RuntimeError, match=<error string here>
):
pass
I have some code like:
if not settings.VAR_URL:
raise Exception("VAR_URL is not defined")
When I try to test it like:
def test_for_absent_env(self):
del os.environ['VAR_URL']
o = Object()
with self.assertRaises(Exception) as error:
o.some_function()
self.assertEqual(error.exception.message, "VAR_URL is not defined")
But it gives KeyError instead of passed test. What should I correct?
That's not the way you should be testing if an exception is raised. There is a specific assertion called assertRaises, which can be used as a context manager (helps to then get the error message to check as well):
with self.assertRaises(Exception) as error:
o.some_cool_function()
self.assertEqual(error.exception.message, "VAR_URL is not defined")
Note that the ability to use assertRaises as a context manager is available in Python>=3.1.
Can Python assert be used to check for specific exceptions in a function? For instance, if I have a function that I know will raise a KeyError, can assert detect it? For example:
def get_value(x):
lookup = {'one': 1}
return lookup[x]
assert get_value('two') == KeyError
When I run this I just get the KeyError exception. Can assert check something like this? Or is that not what assert is used for?
See this: What is the use of "assert" in Python?
assert is for asserting a condition, means verify that this condition has been met else trigger an action.
For your use case, you want to catch an exception, so this is something you want.
#!/usr/bin/env python
import sys
def get_value(x):
lookup = {'one': 1}
return lookup[x]
try:
get_value('two')
except: # catch *all* exceptions
e = sys.exc_info()
print e
This will catch the exception and print it. In this particular case it will print something like:
(<type 'exceptions.KeyError'>, KeyError('two',), <traceback object at 0x102c71c20>)