Handling exception in pytest fixture and pytest.fail vs assert in general - python-3.x

Consider you have something like the following:
import pytest
#pytest.fixture
def foo_fixture():
assert x == 1, "X is not 1"
if x != 1:
pytest.fail("X is not 1")
def test_foo():
assert x == 1, "X is not 1"
if x != 1:
pytest.fail("X is not 1")
Can someone please shed light on differences between raising errors via pytest.fail vs assert,
in both contexts - inside pytest.fixture or inside regular test function.
What is the difference, what is more right\common to do in each case?
Thank in advance

The biggest difference is that pytest.fail() can't be caught and assert can:
for example - this test is going to pass:
def test_assert():
try:
assert False
except:
pass
and this test is going to fail:
def test_pytest_fail():
try:
pytest.fail('failed')
except Exception:
pass
Other than that I don't think there are more major differences because they both raise an AssertionError.
Anyway, I would suggest you use this library https://pypi.org/project/assertpy/ for more cleaner and readable assertions.
**** EDIT: ****
Guess I was wrong:
pytest.fail() can be caught by catching the specific exception Failed or just 'BaseException.
So - they both are ways to raise exceptions but from some reason Failed does not inherit from the generic Exception type.
so pytest.fail() raises Failed exception and not AssertionError

Related

how to throw an error if certain condition evaluates to true

I have below code block:
try:
if str(symbol.args[0]) != str(expr.args[0]):
print('true')
raise SyntaxError('====error')
except:
pass
Here I am trying to raise Syntax error if certain condition is true.I am testing this code block, I can see 'true' is getting printed which means condition is met but even after that also the code is not throwing syntax error.
I am trying to understand what is wrong in the above code.
You're putting pass in the except: block which is swallowing the exception. Either remove the code from the try-except block or change pass to raise
Above answer is pointing the issue, I just want to give some examples to help you better understand how try/except works:
# Just raise an exception (no try/except is needed)
if 1 != 2:
raise ValueError("Values do not match")
# Catch an exception and handle it
a = "1"
b = 2
try:
a += b
except TypeError:
print("Cannot add an int to a str")
# Catch an exception, do something about it and re-raise it
a = "1"
b = 2
try:
a += b
except TypeError:
print("Got to add an int to a str. I'm re-raising the exception")
raise
try/except can also be followed by else and finally, you can check more about these here: try-except-else-finally

Python3 missing exception when looping

I have to define an attribute in a class and I would like to manage error in the most pythonic way.
Here is the code I have tried so far. I can't figure out why I can not "reach" the exception in the following code.
# global variable to be used in the example
my_dict = {"key1": {"property": 10}, "key2": {}}
class Test(object):
#property
def my_attribute(self):
try:
return self._my_attribute
except AttributeError:
self._my_attribute = {}
for key, value in my_dict.items():
print(key)
self._my_attribute[key] = value['property']
except Exception:
print('error')
# I would like to manage my error here with a log or something
print("I am not reaching here")
finally:
return self._my_attribute
if __name__ == '__main__':
Test().my_attribute
I expected to reach the Exception case in the second iteration of the for loop since it is a KeyError ("key2" has no "property"). But it just passes by it. In this example, if the script is run, it does not print "I am not reaching here". Could anyone explain why I am seeing this wrong? Thanks!
The potential KeyError in self._my_attribute[key] = value['property'] is not covered by the except Exception block. Once it is raised the finally block is executed (as a matter of fact the finally block is always executed, regardless of an exception being raised or even handled). This can be easily verified by using a step-by-step debugger or with a simple print('finally') inside the finally block.
This is (among other reasons) why try blocks should be as minimal as possible. If you know that line might raise a KeyError then explicitly try-except it:
for key, value in my_dict.items():
print(key)
try:
self._my_attribute[key] = value['property']
except KeyError as e:
print('Key ', e, 'does not exist')

Hypothesis stateful testing with pytest.raises doesn't report sequence of steps

I want to write a hypothesis.stateful.RuleBasedStateMachine which asserts that an exception is raised under certain circumstances. pytest provides the raises context manager for writing tests about exceptions. If I use pytest.raises inside a hypothesis.stateful.rule, the sequence of steps that lead to the test failure is not reported.
Rewriting the rule without pytest.raises results in the desired behaviour: the sequence of steps is displayed.
Here is some sample code:
from os import getenv
from pytest import raises
from hypothesis.stateful import RuleBasedStateMachine, rule
SHOW_PROBLEM = getenv('SHOW_PROBLEM') == 'yes'
# A state machine which asserts that an exception is raised in under some condition
class FifthCallShouldRaiseValueError(RuleBasedStateMachine):
def __init__(self):
super().__init__()
self.model = Model()
self.count = 0
if SHOW_PROBLEM:
# This version does NOT report the rule sequence
#rule()
def the_rule(self):
self.count += 1
if self.count > 4:
with raises(ValueError):
self.model.method()
else:
# This version DOES report the rule sequence
#rule()
def the_rule(self):
self.count += 1
if self.count > 4:
try:
self.model.method()
except ValueError: assert True
except : assert False
else : assert False
T = FifthCallShouldRaiseValueError.TestCase
# A model that deliberately fails the test, triggering reporting of
# the sequence of steps which lead to the failure.
class Model:
def __init__(self):
self._count = 0
def method(self):
self._count += 1
if self._count > 4:
# Deliberate mistake: raise wrong exception type
raise TypeError
To observe the difference in behaviour, exectue the test with
SHOW_PROBLEM=yes pytest <...>
SHOW_PROBLEM=no pytest <...>
In the second case the output will show
state = FifthCallShouldRaiseValueError()
state.the_rule()
state.the_rule()
state.the_rule()
state.the_rule()
state.the_rule()
state.teardown()
This sequence of steps is missing from the output in the first case. This is usdesirable: the sequence should be show in both cases.
pytest.raises raises Failed: DID NOT RAISE <class 'ValueError'> while the hand-written version raises AssertionError. The former is more informative when it comes to the failure to raise the required exception, but somehow seems to prevent hypothesis.stateful from reporting the sequence of steps, which tells us how we got into that state, and is often the most interesting part of the output.
What can be done to mitigate this, i.e. to ensure that the sequence of steps is printed out, besides not using pytest.raises?
It turns out that the steps are not printed if a rule raises BaseException or a non-Exception subclass. pytest.raises(...) raises just such an error if it doesn't get the expected exception, and there you are.
https://github.com/HypothesisWorks/hypothesis/issues/1372
It's not a particularly gnarly bug now that it's been identified - and thanks for your part in that, by reporting a reproducible case! - so we should get a fix out soon.
Update: this bug was fixed in Hypothesis 3.65.1, on 2018-07-03.

How do you test for an exception in python 3 with pytest when the exceptions are chained

I have some code that uses custom exceptions in python 3.
something like:
def get_my_custom_error():
try:
1.0/0.0
except ZeroDivisionError as err:
raise MyCustomError() from err
and in my test file I have the following:
with pytest.raises(MyCustomError):
get_my_custom_error()
I currently get an output like
ZeroDivisionError
the above exception was the direct cause of the following error:
MyCustomError
This causes the test to fail.
So the code appears to be working, but pytest doesn't seem to be checking the highest level error (which is what I would like it to do).
Python 3.6.1 :: Anaconda 4.4.0
pytest 3.0.7
Any help would be amazing.
Catch and check it explicitly:
try:
get_my_custom_error()
except ZeroDivisionError as err:
assert hasattr(err, '__cause__') and isinstance(err.__cause__, MyCustomError)
Building off phd's answer, you can use pytest.raises to catch the top-level MyCustomError exception, then inspect the object returned by pytest.raises.__enter__ to verify that the chained exception is an instance of ZeroDivisionError.
with pytest.raises(MyCustomError) as exc_info:
get_my_custom_error()
err = exc_info.value
assert hasattr(err, '__cause__')
assert isinstance(err.__cause__, ZeroDivisionError)

How to mock a BulkWriteException in python?

I need to get the information contained in the exception. This is the code I use.
try:
result = yield user_collection.insert_many(content, ordered=False)
except BulkWriteError as e:
print (e)
And in my test when I get into the except with this line,
self.insert_mock.side_effect = [BulkWriteError('')]
it returns me
batch op errors occurred
instead of a MagicMock or a Mock.
How can I mock the BulkWriteError and give it a default return_value and see it when I use print(e)?
Something like this should allow you to test your print was called correctly.
import builtins # mockout print
class BulkWriteErrorStub(BulkWriteError):
''' Stub out the exception so you can bypass the constructor. '''
def __str__:
return 'fake_error'
#mock.patch.object('builtins', 'print')
def testRaisesBulkWrite(self, mock_print):
...
self.insert_mock.side_effect = [BuilkWriteErrorStub]
with self.assertRaises(...):
mock_print.assert_called_once_with('fake_error')
I haven't tested this so feel free to edit it if I made a mistake.

Resources