AssertRaises fails even though exception is raised - python

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.

Related

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.

Mocking a static function call with a long chain of calls in python 2.7

I'm trying to test a large legacy Django application, and I'm getting confused by Python mocking as I have never worked on large Python application.
Specifically, I have method has a long call chain inside that generates an array:
def update(self): # in some class X
# ...
for z in foo.models.Bar.objects.filter(x=1).select('xyz'):
raise Exception("mocked successfully")
I'd like to mock the foo.models.Bar.objects.filter(x=1).select('xyz').
Attempt 1
I've tried several approaches gleaned from various questions, notably using a decorator:
#mock.patch('foo.models.Bar.objects.filter.select')
def test_update(self, mock_select):
mock_select.return_value = [None]
X().update()
I never hit the inside of the mocked call, however- the test should fail due to the exception being raised.
Attempt 2
#mock.patch('foo.models.Bar')
def test_update(self, mock_Bar):
mock_Bar.objects.filter(x=1).select('xyz').return_value = [None]
X().update()
Attempt 3
#mock.patch('foo.models.Bar')
def test_update(self, mock_Bar):
mock_Bar.objects.filter().select().return_value = [None]
X().update()
Attempt 4
I then tried something more basic, to see if I could get an NPE, which didn't work either.
#mock.patch('foo.models.Bar')
def test_update(self, mock_Bar):
mock_Bar.return_value = None
X().update()
All of my attempts pass the test, instead of the exception firing like I expect it to.
It's late so I assume I must be overlooking something basic in the examples I've seen!?
I was able to have it pass by mocking objects. Attempt #3 is close, you just need to change it to filter.return_value.select.return_value to have it pass. Here's my suggestion though as it seems mocking .objects is the preferred way.
#mock.patch('foo.models.Bar.objects')
def test_update(self, mock_bar_objects):
mock_bar_objects.filter.return_value.select.return_value = [None]
X().update()
EDIT: Test run output:
ERROR: test_update (test_x.TestDjango)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/wholevinski/.virtualenvs/p2/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/home/wholevinski/so_test/django_mock/test/test_x.py", line 10, in test_update
X().update()
File "/home/wholevinski/so_test/django_mock/foo/x_module.py", line 6, in update
raise Exception("mocked successfully")
Exception: mocked successfully
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (errors=1)

Can I make Python output exceptions in one line / via logging?

I am using AWS and use AWS cloudwatch to view logs. While things should not break on AWS, they could. I just had such a case. Then I searched for Traceback and just got the lines
Traceback (most recent call last):
without the actual traceback. I have a working structured logging setup (see other question) and I would like to get tracebacks in a similar way.
So instead of:
Traceback (most recent call last):
File "/home/math/Desktop/test.py", line 32, in <module>
adf
NameError: name 'adf' is not defined
something like
{"message": "Traceback (most recent call last):\n File \"/home/math/Desktop/test.py\", line 32, in <module>\n adf\n NameError: name 'adf' is not defined", "lineno": 35, "pathname": "/home/math/Desktop/test.py"}
or even better also with the string in a JSON format.
The only way to achieve this I can think of is a giant try-except block. Pokemon-style. Is there a better solution?
You can use sys.excepthook. It is invoked whenever an exception occurs in your script.
import logging
import sys
import traceback
def exception_logging(exctype, value, tb):
"""
Log exception by using the root logger.
Parameters
----------
exctype : type
value : NameError
tb : traceback
"""
write_val = {'exception_type': str(exctype),
'message': str(traceback.format_tb(tb, 10))}
logging.exception(str(write_val))
Then in your script you have to override the value of sys.excepthook.
sys.excepthook = exception_logging
Now whenever an exception occurs it will be logged with your logger handler.
Note: Don't forget to setup logger before running this
In case somebody wants the exception logged in its default format, but in one line (for any reason), based on the accepted answer:
def exception_logging(exctype, value, tb):
"""
Log exception in one line by using the root logger.
Parameters
----------
exctype : exception type
value : seems to be the Exception object (with its message)
tb : traceback
"""
logging.error(''.join(traceback.format_exception(exctype, value, tb)))
Please also note, that it uses logging.error() instead of logging.exception() which also printed some extra "NoneType: None" line.
Also note that it only seems to work with uncaught exceptions.
For logging caught exceptions, visit How do I can format exception stacktraces in Python logging? and see also my answer.
A slight variation: If you run a Flask application, you can do this:
#app.errorhandler(Exception)
def exception_logger(error):
"""Log the exception."""
logger.exception(str(error))
return str(error)

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

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)

Django model.DoesNotExist exception somehow replaced with an AttributeError

I'm experiencing an odd exception on a Django 1.5 site:
"TypeError: 'exceptions.AttributeError' object is not callable"
Essentially it looks like the model.DoesNotExist exception has been replaced with an AttributeError.
The error is intermittent, but once it happens it seems to 'stick' until I restart the process, which makes me think it might be a case of the model class getting incorrectly set in the course of a particular request, and then persisting.
Bottom of traceback:
File "/opt/mysite/django/apps/profiles/models.py", line 353, in profile_from_cache
profile = self.get(user=user_id)
File "/opt/mysite/.virtualenvs/django/lib/python2.7/site-packages/django/db/models/manager.py", line 143, in get
return self.get_query_set().get(*args, **kwargs)
File "/opt/mysite/.virtualenvs/django/lib/python2.7/site-packages/django/db/models/query.py", line 404, in get
self.model._meta.object_name)
TypeError: 'exceptions.AttributeError' object is not callable
Line of code from django/db/models/query.py:
if not num:
raise self.model.DoesNotExist(
"%s matching query does not exist." %
self.model._meta.object_name)
So it looks as if it's trying to pass a message to the DoesNotExist exception on the model, but it's somehow been replaced by an AttributeError instead.
The issue only seems to happen from http requests - if I do the same action from the command line I just get a DoesNotExist exception (which is what should be happening).
I can't find any obvious reason this should be happening. Any ideas?
(PS this seems to be the same issue. The user's answer to it, I think, is wrong: https://groups.google.com/forum/#!topic/django-users/k9JMyXlUt3Q)
Possibly relevant code
Here is an outline of the model manager:
class CacheManager(models.Manager):
def profile_from_cache(self, user_id=None):
profile = cache.get("profile_%s" % user_id)
if profile is None:
try:
profile = self.get(user=user_id)
except Profile.DoesNotExist:
return None
cache.set("profile_%s" % user_id, profile, settings.CACHE_TIMEOUT)
return profile
...
class Profile(models.Model):
...
caches = CacheManager()
Here's the line of code that seems to be causing the error. In this case it's in some middleware, but there are a few different places, all causing the same thing.
Profile.caches.profile_from_cache(user_id=request.user.pk)
It was because of incorrect syntax elsewhere in the project, that caught multiple exceptions like this:
try:
do_something()
except AttributeError, Profile.DoesNotExist:
pass
When it should have been this:
try:
do_something()
except (AttributeError, Profile.DoesNotExist):
pass
What was happening was that when this happened, AttributeError got assigned to Profile.DoesNotExist in memory, so when it was raised later, it was the wrong exception.
Thanks to this post for helping.

Categories