Error while testing the raise of self-defined exceptions (using assertRaises()) - python

I am creating tests for a python project. The normal tests work just fine, however I want to test if in a certain condition my function raises a self-defined exception. Therefor I want to use assertRaises(Exception, Function). Any ideas?
The function that raises the exception is:
def connect(comp1, comp2):
if comp1 == comp2:
raise e.InvalidConnectionError(comp1, comp2)
...
The exception is:
class InvalidConnectionError(Exception):
def __init__(self, connection1, connection2):
self._connection1 = connection1
self._connection2 = connection2
def __str__(self):
string = '...'
return string
The test method is the following:
class TestConnections(u.TestCase):
def test_connect_error(self):
comp = c.PowerConsumer('Bus', True, 1000)
self.assertRaises(e.InvalidConnectionError, c.connect(comp, comp))
However I get the following error:
Error
Traceback (most recent call last):
File "C:\Users\t5ycxK\PycharmProjects\ElectricPowerDesign\test_component.py", line 190, in test_connect_error
self.assertRaises(e.InvalidConnectionError, c.connect(comp, comp))
File "C:\Users\t5ycxK\PycharmProjects\ElectricPowerDesign\component.py", line 428, in connect
raise e.InvalidConnectionError(comp1, comp2)
InvalidConnectionError: <unprintable InvalidConnectionError object>

assertRaises expects to actually perform the call. Yet, you already perform it by yourself, thereby throwing the error before assertRaises actually executes.
self.assertRaises(e.InvalidConnectionError, c.connect(comp, comp))
# run this ^ with first static argument ^ and second argument ^ from `c.connect(comp, comp)`
Use either of those instead:
self.assertRaises(e.InvalidConnectionError, c.connect, comp, comp)
with self.assertRaises(e.InvalidConnectionError):
c.connect(comp, comp)

Related

python - assert_called_with where AttributeError is passed as arg

I am trying to unit test a custom exception called TargetException.
One of the arguments to this exception is itself an exception.
Here's the relevant part of my test:
mock_exception.assert_called_once_with(
id,
AttributeError('invalidAttribute',)
)
Here's the test failure message:
File "/usr/local/lib/python2.7/site-packages/mock/mock.py", line 948, in assert_called_once_with
return self.assert_called_with(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/mock/mock.py", line 937, in assert_called_with
six.raise_from(AssertionError(_error_message(cause)), cause)
File "/usr/local/lib/python2.7/site-packages/six.py", line 718, in raise_from
raise value
AssertionError: Expected call: TargetException(<testrow.TestRow object at 0x7fa2611e7050>, AttributeError('invalidAttribute',))
Actual call: TargetException(<testrow.TestRow object at 0x7fa2611e7050>, AttributeError('invalidAttribute',))
In both the "Expected call" and the "Action call," the same arguments are present -- at least it appears that way to me.
Do I need to pass the AttributeError a different way to resolve the error?
The problem is that you compare the instances of the contained exception. As the AttributeError instance created in the tested function and the instance used for comparison in test are different, the assertion fails.
What you could do instead is testing the called arguments separately to ensure they are of the correct type:
#mock.patch('yourmodule.TargetException')
def test_exception(mock_exception):
# call the tested function
...
mock_exception.assert_called_once()
assert len(mock_exception.call_args[0]) == 2 # shall be called with 2 positional args
arg1 = mock_exception.call_args[0][0] # first argument
assert isinstance(arg1, testrow.TestRow) # type of the first arg
... # more tests for arg1
arg2 = mock_exception.call_args[0][1] # second argument
assert isinstance(arg2, AttributeError) # type of the second arg
assert str(arg2) == 'invalidAttribute' # string value of the AttributeError
E.g. you test for the class and for the relevant values of the arguments separately. Using assert_called_with works only for PODs, or if you already know the called instance (for example if it is a singleton, or a known mock).
Building on top of MrBean Bremen's answer.
Another way to solve this is to hold the exception in a var and check on the var:
ex = AttributeError('invalidAttribute',)
...
def foo():
raise ex
...
mock_exception.assert_called_once_with(
id,
ex
)

Unit test "fail" method unable to find the reference to the test class

I am using python unittest to test a web app using selenium. In my teardownClass, I am calling cls.fail but it returns "AttributeError" saying that "failureException" not found in the "string".
Here is what my teardownClass method looks like:
#classmethod
def tearDownClass(cls):
browser_logs = cls.driver.get_log("browser")
errors = [log_entry['message'] for log_entry in browser_logs if logentry['level'] == 'SEVERE']
if errors:
cls.fail(errors)
this returns the following attributeError:
======================================================================
ERROR: tearDownClass (__main__.unitTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/shahrukh/Desktop/apollotest/test_users.py", line 239, in tearDownClass
cls.fail("error")
File "/usr/lib/python3.6/unittest/case.py", line 670, in fail
raise self.failureException(msg)
AttributeError: 'str' object has no attribute 'failureException'
----------------------------------------------------------------------
"fail" method from /usr/lib/python3.6/unittest/case.py file:
def fail(self, msg=None):
"""Fail immediately, with the given message."""
raise self.failureException(msg)
This method is part of "Testcase" class
The problem is that fail method above does not find the reference to self. "errors" in my code is a string object. So when I call cls.fail(errors) it considers self=errors and msg remains None.
I don't get this problem if I change my code statement to cls.fail(cls, errors).
I want to understand why am I experiencing this behavior? Am I missing anything because according to my understanding cls.fail should mean that self in fail method is equal to cls.
Would really appreciate some help.

#mock.patch isn't raising an attribute error even after setting side_effect

I'm attempting to fix a bug in the python package caniusepython3 which arises because distlib isn't parsing pypi projects correctly. I've written this unit test
#mock.patch('distlib.locators.locate')
def test_blocking_dependencies_locators_fails(self, distlib_mock):
"""
Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
"""
py3 = {'py3_project': ''}
breaking_project = 'test_project'
distlib_mock.locators.locate.return_value = "foo"
distlib_mock.locators.locate.side_effect = AttributeError()
got = dependencies.blocking_dependencies([breaking_project], py3)
# If you'd like to test that a message is logged we can use
# testfixtures.LogCapture or stdout redirects.
So that when distlib fixes the error in the next release of distlib the test case will still be valid.
The problem is that the MagicMock never raises a AttributeError as I expected and instead returns a string representation of the magic mock object
try:
# sets dist to <MagicMock name='locate()' id='4447530792'>
dist = distlib.locators.locate(project)
except AttributeError:
# This is a work around //bitbucket.org/pypa/distlib/issue/59/
log.warning('{0} found but had to be skipped.'.format(project))
continue
And causes this stack trace later on because it returns the object repr,
======================================================================
ERROR: Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/mock.py", line 1136, in patched
return func(*args, **keywargs)
File "/Users/alexlord/git/caniusepython3/caniusepython3/test/test_dependencies.py", line 81, in test_blocking_dependencies_locators_fails
got = dependencies.blocking_dependencies([breaking_project], py3)
File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 119, in blocking_dependencies
return reasons_to_paths(reasons)
File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 43, in reasons_to_paths
parent = reasons[blocker]
File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 29, in __getitem__
return super(LowerDict, self).__getitem__(key.lower())
nose.proxy.KeyError: <MagicMock name='locate().name.lower().lower()' id='4345929400'>
-------------------- >> begin captured logging << --------------------
ciu: INFO: Checking top-level project: test_project ...
ciu: INFO: Locating <MagicMock name='locate().name.lower()' id='4344734944'>
ciu: INFO: Dependencies of <MagicMock name='locate().name.lower()' id='4344734944'>: []
--------------------- >> end captured logging << ---------------------
Why is the MagicMock not returning an exception when distlib.locator.locate() is called?
Update: I was able to get this unit test to work when I switched to using
def test_blocking_dependencies_locators_fails(self):
"""
Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
"""
with mock.patch.object(distlib.locators, 'locate') as locate_mock:
py3 = {'py3_project': ''}
breaking_project = 'test_project'
locate_mock.side_effect = AttributeError()
got = dependencies.blocking_dependencies([breaking_project], py3)
# If you'd like to test that a message is logged we can use
# testfixtures.LogCapture or stdout redirects.
But I'm still wondering what I did wrong with the decorator format.
When you use #mock.patch, it mocks what you tell it, and passes that mock object as a parameter. Thus, your distlib_mock parameter is the mock locate function. You're effectively setting attributes on distlib.locators.locate.locators.locate. Set the attributes directly on the provided mock, and things should work better.
#mock.patch('distlib.locators.locate')
def test_blocking_dependencies_locators_fails(self, locate_mock):
# ...
locate_mock.return_value = "foo"
locate_mock.side_effect = AttributeError()
# ...

implementing a deferred exception in Python

I would like to implement a deferred exception in Python that is OK to store somewhere but as soon as it is used in any way, it raises the exception that was deferred. Something like this:
# this doesn't work but it's a start
class DeferredException(object):
def __init__(self, exc):
self.exc = exc
def __getattr__(self, key):
raise self.exc
# example:
mydict = {'foo': 3}
try:
myval = obtain_some_number()
except Exception as e:
myval = DeferredException(e)
mydict['myval'] = myval
def plus_two(x):
print x+2
# later on...
plus_two(mydict['foo']) # prints 5
we_dont_use_this_val = mydict['myval'] # Always ok to store this value if not used
plus_two(mydict['myval']) # If obtain_some_number() failed earlier,
# re-raises the exception, otherwise prints the value + 2.
The use case is that I want to write code to analyze some values from incoming data; if this code fails but the results are never used, I want it to fail quietly; if it fails but the results are used later, then I'd like the failure to propagate.
Any suggestions on how to do this? If I use my DeferredException class I get this result:
>>> ke = KeyError('something')
>>> de = DeferredException(ke)
>>> de.bang # yay, this works
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __getattr__
KeyError: 'something'
>>> de+2 # boo, this doesn't
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'DeferredException' and 'int'
Read section 3.4.12 of the docs, "Special method lookup for new-style classes." It explains exactly the problem you have encountered. The normal attribute lookup is bypassed by the interpreter for certain operators, such as addition (as you found out the hard way). Thus the statement de+2 in your code never calls your getattr function.
The only solution, according to that section, is to insure that "the special method must be set on the class object itself in order to be consistently invoked by the interpreter."
Perhaps you'd be better off storing all your deferred exceptions in a global list, wrapping your entire program in a try:finally: statement, and printing out the whole list in the finally block.

AssertRaises fails even though exception is raised

I am running into the following rather strange problem:
I am developing a django app and in my models class I am defining an exception that should be raised when a validation fails:
class MissingValueException(Exception):
"""Raise when a required attribute is missing."""
def __init__(self, message):
super(MissingValueException, self).__init__()
self.message = message
def __str__(self):
return repr(self.message)
This code is called from a publication class in a validation method:
def validate_required_fields(self):
# Here is the validation code.
if all_fields_present:
return True
else:
raise MissingValueException(errors)
In my unit test I create a case where the exception should be raised:
def test_raise_exception_incomplete_publication(self):
publication = Publication(publication_type="book")
self.assertRaises(MissingValueException, publication.validate_required_fields)
This produces the following output:
======================================================================
ERROR: test_raise_exception_incomplete_publication (core_knowledge_platform.core_web_service.tests.logic_tests.BusinessLogicTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/media/data/Dokumente/Code/master_project/core_knowledge_platform/../core_knowledge_platform/core_web_service/tests/logic_tests.py", line 45, in test_raise_exception_incomplete_publication
self.assertRaises(MissingValueException, method, )
File "/usr/lib/python2.7/unittest/case.py", line 465, in assertRaises
callableObj(*args, **kwargs)
File "/media/data/Dokumente/Code/master_project/core_knowledge_platform/../core_knowledge_platform/core_web_service/models.py", line 150, in validate_required_fields
raise MissingValueException(errors)
MissingValueException: 'Publication of type book is missing field publisherPublication of type book is missing field titlePublication of type book is missing field year'
So it looks like the exception is raised (which is the case - I even checked it in an interactive IPython session), but it seems that assertRaises is not catching it.
Anyone has any idea why this might happen?
Thanks
This could happen if your tests and your product code are importing your exception class through two different paths, so asserRaises doesn't realize that the exception you got was the one you were looking for.
Look at your imports, make sure that they are the same in both places. Having the same directories available in two different ways in your PYTHONPATH can make this happen. Symbolic links in those entries can also confuse things.

Categories