doremi
doremi

Reputation: 15329

How to test custom exception, message with Python Nose

My custom exception classes:

class MyCustomException(Exception):
    pass

class MyCustomRestException(MyCustomException):

    def __init__(self, status, uri, msg=""):
        self.uri = uri
        self.status = status
        self.msg = msg
        super(MyCustomException, self).__init__(msg)

    def __str__(self):
        return "HTTP ERROR %s: %s \n %s" % (self.status, self.msg, self.uri)

My Test

# note: @raises(MyCustomRestException) works by itself
@raises(MyCustomRestException, 'HTTP ERROR 403: Invalid User')
def test_bad_token():
    sc = SomeClass('xxx', account_number)
    result = ec.method_that_generates_exception()

Here's what nose spits back out

12:52:13 ~/sandbox/ec$ nosetests -v
Failure: AttributeError ('str' object has no attribute '__name__') ... ERROR

======================================================================
ERROR: Failure: AttributeError ('str' object has no attribute '__name__')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/nose/loader.py", line 390, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/local/lib/python2.7/site-packages/nose/importer.py", line 39, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/local/lib/python2.7/site-packages/nose/importer.py", line 86, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/ec/tests/my_client_tests.py", line 16, in <module>
    @raises(MyCustomRestException, 'HTTP ERROR 403: Invalid User')
  File "/usr/local/lib/python2.7/site-packages/nose/tools/nontrivial.py", line 55, in raises
    valid = ' or '.join([e.__name__ for e in exceptions])
AttributeError: 'str' object has no attribute '__name__'

----------------------------------------------------------------------
Ran 1 test in 0.012s

FAILED (errors=1)

So ...

My questions is two fold:

Solution: with help from alynna (below)

This works great.

def test_bad_token():
    sc = SomeClass('xxx', account_number)

    with assert_raises(MyCustomRestException) as e:
        sc.method_that_generates_exception()

    assert_equal(e.exception.status, 403)
    assert_equal(e.exception.msg, 'Invalid User')

Upvotes: 2

Views: 3962

Answers (2)

jkozera
jkozera

Reputation: 3066

Nose's @raises decorator doesn't support checking anything else than exception's class. Passing more arguments means you want to allow more types of exceptions to be interpreted as valid. Hence Nose interprets the string you're passing as a exception and can't find it's __name__. (see docs)

To fix it, you could to implement additional decorator for your custom exception, like:

from nose.tools import eq_
from functools import wraps

def raises_custom(status=None, uri=None, msg=None):
    assert status or uri or msg, 'You need to pass either status, uri, or msg'
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            try:
                function(*args, **kwargs)
            except MyCustomException, e:
                def check(value, name):
                    if value:
                        eq_(getattr(exception, name), value)
                check(status, 'status')
                check(uri, 'uri')
                check(msg, 'msg')
            except:
                raise
            else:
                message = "%{} did not raise MyCustomException".format(\
                    function.__name__)
                raise AssertionError(message)
        return wrapper
    return decorator

and then use it like @raises_custom(msg="HTTP ERROR 403: Invalid User").

(I didn't test the code above, just meant to give a rough outline of how it could look like)

UPDATE: Using assertRaises, as alynna's suggested, is probably cleaner though. Especially it's better if you can identify a specific place where exception should occur - as opposed to wrapping whole function with a decorator.

Upvotes: 1

lavaturtle
lavaturtle

Reputation: 2671

I think your problem is that the arguments to the @raises decorator are expected to all be exception classes: https://nose.readthedocs.org/en/latest/testing_tools.html#nose.tools.raises

You might want assertRaises instead. The documentation shows it being used to test extra attributes of exceptions: http://docs.python.org/2/library/unittest.html#unittest.TestCase.assertRaises

Upvotes: 2

Related Questions