tyring to test for exception using python unittest - python

I have the following code:
class cutomTests(unittest.TestCase, moduleName.className):
def test_input_too_large(self):
'''className.functionName should fail with large input'''
self.assertRaises(moduleName.OutOfRangeError, self.functionName(4000)
I got the following result:
======================================================================
ERROR: test_input_too_large (__main__.customTests)
className.functionName should fail with large input
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/user/workspace//ClassNameTests.py", line 37, in test_input_too_large
self.assertRaises(toRoman.OutOfRangeError, self.answer(4000))
File "/home/user/workspace/moduleName.py", line 47, in answer
raise OutOfRangeError()
OutOfRangeError
So the results should be pass right because the exception is raised???

You need to let assertRaises call the function rather than doing it yourself:
self.assertRaises(OutOfRangeError, self.functionName, 4000)
Right now, you're calling self.functionName and passing its result to assertRaises. Obviously, if self.functionName raises and exception, it won't get caught by assertRaises because assertRaises won't have been called yet at that point :-).
Note that as of python2.7, assertRaises can be used as a context manager which is a lot more convenient:
with self.assertRaises(OutOfRangeError):
self.functionName(4000)

No. You should use assertRaises method as context manager.
with self.assertRaises(moduleName.OutOfRangeError):
self.functionName(4000)
Another usage is to pass function and arguments as next arguments. However it's not that pretty:
self.assertRaises(moduleName.OutOfRangeError, self.functionName, 4000)

Related

How to test for raised exception in Python/Django?

I've got a Django project in which I have a function which I want to test. Simplified, the function looks like this:
class InvalidUrlError(Exception):
pass
def get_info_from_url(url):
try:
return url.split(':')[1].split('/')[0]
except Exception:
raise InvalidUrlError(f"Invalid url: {url}")
And my test looks like this:
class ToolsTestCase(TestCase):
def test_get_info_from_url_wrong_formatted_url(self):
self.assertRaises(InvalidUrlError, get_info_from_url("https://acc.images.data.ourcompany.com/"))
When I run it though, I get the following output:
$ ./manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
....E
======================================================================
ERROR: test_get_info_from_url_wrong_formatted_url (checker.tests.ToolsTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/kramer65/repos/auth-proxy/app/checker/tools.py", line 10, in get_info_from_url
return url.split(':')[1].split('/')[0]
IndexError: list index out of range
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/kramer65/repos/auth-proxy/app/checker/tests.py", line 57, in test_get_info_from_url_wrong_formatted_url
self.assertRaises(InvalidUrlError, get_info_from_url("https://acc.images.data.ourcompany.com/"))
File "/home/kramer65/repos/auth-proxy/app/checker/tools.py", line 15, in get_info_from_url
raise InvalidUrlError(f"Invalid url: {url}")
checker.tools.InvalidUrlError: Invalid url: https://acc.images.data.ourcompany.com/
----------------------------------------------------------------------
Ran 5 tests in 0.037s
FAILED (errors=1)
Destroying test database for alias 'default'...
Why does it raise the exceptions, instead of passing the tests? I think I do a comparable thing in another test, which works great.
Does anybody know what I'm doing wrong here?
You need to pass a callable instead of calling the function itself. Unittest docs for assertRaises
So change it to:
class ToolsTestCase(TestCase):
def test_get_info_from_url_wrong_formatted_url(self):
self.assertRaises(InvalidUrlError, get_info_from_url, "https://acc.images.data.ourcompany.com/")
Other option is to use assertRaises as context manager like this:
class ToolsTestCase(TestCase):
def test_get_info_from_url_wrong_formatted_url(self):
with self.assertRaises(InvalidUrlError):
get_info_from_url("https://acc.images.data.ourcompany.com/")

Can't catch mocked exception because it doesn't inherit BaseException

I'm working on a project that involves connecting to a remote server, waiting for a response, and then performing actions based on that response. We catch a couple of different exceptions, and behave differently depending on which exception is caught. For example:
def myMethod(address, timeout=20):
try:
response = requests.head(address, timeout=timeout)
except requests.exceptions.Timeout:
# do something special
except requests.exceptions.ConnectionError:
# do something special
except requests.exceptions.HTTPError:
# do something special
else:
if response.status_code != requests.codes.ok:
# do something special
return successfulConnection.SUCCESS
To test this, we've written a test like the following
class TestMyMethod(unittest.TestCase):
def test_good_connection(self):
config = {
'head.return_value': type('MockResponse', (), {'status_code': requests.codes.ok}),
'codes.ok': requests.codes.ok
}
with mock.patch('path.to.my.package.requests', **config):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.SUCCESS
)
def test_bad_connection(self):
config = {
'head.side_effect': requests.exceptions.ConnectionError,
'requests.exceptions.ConnectionError': requests.exceptions.ConnectionError
}
with mock.patch('path.to.my.package.requests', **config):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.FAILURE
)
If I run the function directly, everything happens as expected. I even tested by adding raise requests.exceptions.ConnectionError to the try clause of the function. But when I run my unit tests, I get
ERROR: test_bad_connection (test.test_file.TestMyMethod)
----------------------------------------------------------------
Traceback (most recent call last):
File "path/to/sourcefile", line ###, in myMethod
respone = requests.head(address, timeout=timeout)
File "path/to/unittest/mock", line 846, in __call__
return _mock_self.mock_call(*args, **kwargs)
File "path/to/unittest/mock", line 901, in _mock_call
raise effect
my.package.requests.exceptions.ConnectionError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "Path/to/my/test", line ##, in test_bad_connection
mypackage.myMethod('some_address',
File "Path/to/package", line ##, in myMethod
except requests.exceptions.ConnectionError:
TypeError: catching classes that do not inherit from BaseException is not allowed
I tried to change the exception I was patching in to BaseException and I got a more or less identical error.
I've read https://stackoverflow.com/a/18163759/3076272 already, so I think it must be a bad __del__ hook somewhere, but I'm not sure where to look for it or what I can even do in the mean time. I'm also relatively new to unittest.mock.patch() so it's very possible that I'm doing something wrong there as well.
This is a Fusion360 add-in so it is using Fusion 360's packaged version of Python 3.3 - as far as I know it's a vanilla version (i.e. they don't roll their own) but I'm not positive of that.
I could reproduce the error with a minimal example:
foo.py:
class MyError(Exception):
pass
class A:
def inner(self):
err = MyError("FOO")
print(type(err))
raise err
def outer(self):
try:
self.inner()
except MyError as err:
print ("catched ", err)
return "OK"
Test without mocking :
class FooTest(unittest.TestCase):
def test_inner(self):
a = foo.A()
self.assertRaises(foo.MyError, a.inner)
def test_outer(self):
a = foo.A()
self.assertEquals("OK", a.outer())
Ok, all is fine, both test pass
The problem comes with the mocks. As soon as the class MyError is mocked, the expect clause cannot catch anything and I get same error as the example from the question :
class FooTest(unittest.TestCase):
def test_inner(self):
a = foo.A()
self.assertRaises(foo.MyError, a.inner)
def test_outer(self):
with unittest.mock.patch('foo.MyError'):
a = exc2.A()
self.assertEquals("OK", a.outer())
Immediately gives :
ERROR: test_outer (__main__.FooTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "...\foo.py", line 11, in outer
self.inner()
File "...\foo.py", line 8, in inner
raise err
TypeError: exceptions must derive from BaseException
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<pyshell#78>", line 8, in test_outer
File "...\foo.py", line 12, in outer
except MyError as err:
TypeError: catching classes that do not inherit from BaseException is not allowed
Here I get a first TypeErrorthat you did not have, because I am raising a mock while you forced a true exception with 'requests.exceptions.ConnectionError': requests.exceptions.ConnectionError in config. But the problem remains that the except clause tries to catch a mock.
TL/DR: as you mock the full requests package, the except requests.exceptions.ConnectionError clause tries to catch a mock. As the mock is not really a BaseException, it causes the error.
The only solution I can imagine is not to mock the full requests but only the parts that are not exceptions. I must admit I could not find how to say to mock mock everything except this but in your example, you only need to patch requests.head. So I think that this should work :
def test_bad_connection(self):
with mock.patch('path.to.my.package.requests.head',
side_effect=requests.exceptions.ConnectionError):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.FAILURE
)
That is : only patch the head method with the exception as side effect.
I just ran into the same issue while trying to mock sqlite3 (and found this post while looking for solutions).
What Serge said is correct:
TL/DR: as you mock the full requests package, the except requests.exceptions.ConnectionError clause tries to catch a mock. As the mock is not really a BaseException, it causes the error.
The only solution I can imagine is not to mock the full requests but only the parts that are not exceptions. I must admit I could not find how to say to mock mock everything except this
My solution was to mock the entire module, then set the mock attribute for the exception to be equal to the exception in the real class, effectively "un-mocking" the exception. For example, in my case:
#mock.patch(MyClass.sqlite3)
def test_connect_fail(self, mock_sqlite3):
mock_sqlite3.connect.side_effect = sqlite3.OperationalError()
mock_sqlite3.OperationalError = sqlite3.OperationalError
self.assertRaises(sqlite3.OperationalError, MyClass, self.db_filename)
For requests, you could assign exceptions individually like this:
mock_requests.exceptions.ConnectionError = requests.exceptions.ConnectionError
or do it for all of the requests exceptions like this:
mock_requests.exceptions = requests.exceptions
I don't know if this is the "right" way to do it, but so far it seems to work for me without any issue.
For those of us who need to mock an exception and can't do that by simply patching head, here is an easy solution that replaces the target exception with an empty one:
Say we have a generic unit to test with an exception we have to have mocked:
# app/foo_file.py
def test_me():
try:
foo()
return "No foo error happened"
except CustomError: # <-- Mock me!
return "The foo error was caught"
We want to mock CustomError but because it is an exception we run into trouble if we try to patch it like everything else. Normally, a call to patch replaces the target with a MagicMock but that won't work here. Mocks are nifty, but they do not behave like exceptions do. Rather than patching with a mock, let's give it a stub exception instead. We'll do that in our test file.
# app/test_foo_file.py
from mock import patch
# A do-nothing exception we are going to replace CustomError with
class StubException(Exception):
pass
# Now apply it to our test
#patch('app.foo_file.foo')
#patch('app.foo_file.CustomError', new_callable=lambda: StubException)
def test_foo(stub_exception, mock_foo):
mock_foo.side_effect = stub_exception("Stub") # Raise our stub to be caught by CustomError
assert test_me() == "The error was caught"
# Success!
So what's with the lambda? The new_callable param calls whatever we give it and replaces the target with the return of that call. If we pass our StubException class straight, it will call the class's constructor and patch our target object with an exception instance rather than a class which isn't what we want. By wrapping it with lambda, it returns our class as we intend.
Once our patching is done, the stub_exception object (which is literally our StubException class) can be raised and caught as if it were the CustomError. Neat!
I faced a similar issue while trying to mock the sh package. While sh is very useful, the fact that all methods and exceptions are defined dynamically make it more difficult to mock them. So following the recommendation of the documentation:
import unittest
from unittest.mock import Mock, patch
class MockSh(Mock):
# error codes are defined dynamically in sh
class ErrorReturnCode_32(BaseException):
pass
# could be any sh command
def mount(self, *args):
raise self.ErrorReturnCode_32
class MyTestCase(unittest.TestCase):
mock_sh = MockSh()
#patch('core.mount.sh', new=mock_sh)
def test_mount(self):
...
I just ran into the same problem when mocking struct.
I get the error:
TypeError: catching classes that do not inherit from BaseException is not allowed
When trying to catch a struct.error raised from struct.unpack.
I found that the simplest way to get around this in my tests was to simply set the value of the error attribute in my mock to be Exception. For example
The method I want to test has this basic pattern:
def some_meth(self):
try:
struct.unpack(fmt, data)
except struct.error:
return False
return True
The test has this basic pattern.
#mock.patch('my_module.struct')
def test_some_meth(self, struct_mock):
'''Explain how some_func should work.'''
struct_mock.error = Exception
self.my_object.some_meth()
struct_mock.unpack.assert_called()
struct_mock.unpack.side_effect = struct_mock.error
self.assertFalse(self.my_object.some_meth()
This is similar to the approach taken by #BillB, but it is certainly simpler as I don't need to add imports to my tests and still get the same behavior. To me it would seem this is the logical conclusion to the general thread of reasoning in the answers here.
Use patch.object to partially mock a class.
My use case:
import unittest
from unittest import mock
import requests
def test_my_function(self):
response = mock.MagicMock()
response.raise_for_status.side_effect = requests.HTTPError
with mock.patch.object(requests, 'get', return_value=response):
my_function()

Python: assertRaises( ) not catching ldap.SERVER_DOWN error when raised

Thanks for your help in advance.
I've got the following class method that I'm trying to test:
def _get_ldap_connection(self):
"""
Instantiate and return simpleldap.Connection object.
Raises:
ldap.SERVER_DOWN: When ldap_url is invalid or server is
not reachable.
"""
try:
ldap_connection = simpleldap.Connection(
self.ldap_url, encryption='ssl', require_cert=False,
debug=False, dn=self.ldap_login_dn,
password=self.ldap_login_password)
except ldap.SERVER_DOWN:
raise ldap.SERVER_DOWN(
"The LDAP server specified, {}, did not respond to the "
"connection attempt.".format(self.ldap_url))
And here's the unittest:
def test__get_ldap_connection(self):
"""
VERY IMPORTANT: This test refers to your actual config.json file.
If it is correctly populated, you can expect this test to fail.
"""
# Instantiate Class
test_extractor = SakaiLdapExtractor('config_files/config.json')
# Monkey with ldap server url to ensure error.
test_extractor.ldap_url = "invalid_ldap_url"
self.assertRaises(
ldap.SERVER_DOWN, test_extractor._get_ldap_connection())
So far, so good. But when I execute the unit tests (via nose) test_extractor._get_ldap_connection() is called from the assertRaises statement, but the exception is not caught and the test fails.
Here is the output:
vagrant#precise64:/vagrant/sakai-directory-integration$ nosetests
...E..
======================================================================
ERROR: VERY IMPORTANT: This test refers to your actual config.json file.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/vagrant/sakai-directory-integration/test_sakaiLdapExtractor.py", line 77, in test__get_ldap_connection
ldap.SERVER_DOWN, test_extractor._get_ldap_connection())
File "/vagrant/sakai-directory-integration/sakai_ldap_integration.py", line 197, in _get_ldap_connection
"connection attempt.".format(self.ldap_url))
SERVER_DOWN: The LDAP server specified, invalid_ldap_url, did not respond to the connection attempt.
----------------------------------------------------------------------
Ran 6 tests in 0.159s
Help me!
Don't call, just pass function (method) itself; drop ():
self.assertRaises(
ldap.SERVER_DOWN, test_extractor._get_ldap_connection)
Alternatively, you can use with self.assertRaises(..) form if you are using recent version of python (Python 2.7+ / Python 3.1+):
with self.assertRaises(ldap.SERVER_DOWN):
test_extractor._get_ldap_connection()
You are not using assertRaises correctly.
You can use it as a context manager:
with self.assertRaises(ldap.SERVER_DOWN):
test_extractor._get_ldap_connection()
or the usual way (self.assertRaises(exception, function, args):
self.assertRaises(ldap.SERVER_DOWN, test_extractor._get_ldap_connection)
Also see:
How to properly use unit-testing's assertRaises() with NoneType objects?
Testing in Python - how to use assertRaises in testing using unittest?
documentation

Nose: generator for TestCase-based classes

I want to create a generator for variations of a TestCase-derived class.
What I tried is this:
import unittest
def create_class(param):
class Test(unittest.TestCase):
def setUp(self):
pass
def test_fail(self):
assert False
return Test
def test_basic():
for i in range(5):
yield create_class(i)
What I get is this:
======================================================================
ERROR: test_1.test_basic
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.3/site-packages/nose/case.py", line 268, in setUp
try_run(self.test, names)
File "/usr/lib/python3.3/site-packages/nose/util.py", line 478, in try_run
return func()
TypeError: setUp() missing 1 required positional argument: 'self'
Yielding instances instead of classes (yield create_class(i)()) leaves me with this error:
======================================================================
ERROR: test_1.test_basic
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.3/site-packages/nose/case.py", line 198, in runTest
self.test(*self.arg)
File "/usr/lib/python3.3/unittest/case.py", line 492, in __call__
return self.run(*args, **kwds)
File "/usr/lib/python3.3/unittest/case.py", line 423, in run
testMethod = getattr(self, self._testMethodName)
AttributeError: 'Test' object has no attribute 'runTest'
Any ideas?
When instantiating a TestCase you should pass the method name of the test:
yield create_class(i)('test_fail')
Otherwise the name defaults to runTest(and thus the last error you got).
Also note that there is a strange interaction between test generators and TestCase. With the following code:
import unittest
def create_class(param):
class Test(unittest.TestCase):
def setUp(self):
pass
def test_fail(self):
print('executed')
assert False
print('after assert')
return Test
def test_basic():
for i in range(5):
yield create_class(i)('test_fail')
I obtain this output:
$ nosetests -s
executed
.executed
.executed
.executed
.executed
.
----------------------------------------------------------------------
Ran 5 tests in 0.004s
OK
As you can see the test does not fail, even though the assert works. This is probably due to the fact that TestCase handles the AssertionError but nose does not expect this to be handled and thus it cannot see that the test failed.
This can be seen from the documentation of TestCase.run:
Run the test, collecting the result into the test result object passed as result. If result is omitted or None, a temporary result
object is created (by calling the defaultTestResult() method) and
used. The result object is not returned to run()‘s caller.
The same effect may be had by simply calling the TestCase instance.
So, nose doesn't see that the objected yielded by the generator is a TestCase which should be handled in a special manner, it simply expects a callable. The TestCase is run, but the result is put into a temporary object that is lost, and this eats all test failures that happen inside the tests. Hence yielding TestCasees simply doesn't work.
I have run the codes you provides. I received no error. The version I use is python2.7. System is ubuntu12.10. Maybe you need to check with python2.7.

Catching unpickleable exceptions and re-raising

This is a followup to my question Hang in Python script using SQLAlchemy and multiprocessing. As discussed in that question, pickling exceptions is problematic in Python. This is usually not a issue, but one case when it is, is when errors occur in the python multiprocessing module. Since multiprocessing moves objects around by pickling, if an error occurs inside a multiprocessing process, the entire process may hang, as demonstrated in that question.
One possible approach is to fix all the problematic exceptions, as discussed in that question. This is not easy, since one cannot easily know in advance which exceptions may be called. An alternative approach, which was suggested by lbolla in an answer to the question, is to catch the exception, construct an equivalent harmless exception, and then rethrow.
However, I'm not sure of exactly how to do this. Consider the following code.
class BadExc(Exception):
def __init__(self, message, a):
'''Non-optional param in the constructor.'''
Exception.__init__(self, message)
self.a = a
import sys
try:
try:
#print foo
raise BadExc("bad exception error message", "a")
except Exception, e:
raise Exception(e.__class__.__name__ + ": " +str(e)), None, sys.exc_info()[2]
except Exception, f:
pass
import cPickle
a = cPickle.dumps(f)
l = cPickle.loads(a)
print "raising error"
raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]
This code pickles and unpickles the exception, and then throws it, giving the error
raising error
Traceback (most recent call last):
File "<stdin>", line 11, in <module>
Exception: BadExc: bad exception error message
Credits to Glenn Maynard's answer to "“Inner exception” (with traceback) in Python?".
This has the important stuff, namely the traceback, the error message, and the exception type, so this might be the best one can do. But ideally I'd like something that looks exactly like the original exception, namely
Traceback (most recent call last):
File "<stdin>", line 11, in <module>
__main__.BadExc: bad exception error message
or more generally, with the name of the exception in the front, rather than Exception. Is this possible?
Alternatively, instead of the BadExc class, one can use the print foo statement instead, which gives a NameError. However, this exception does not require special handling.
You can override sys.excepthook to achieve what you want. It at least works for this example, but it's pretty hacky so please test and no promises :-)
import sys
def excepthook_wrapper(type, value, traceback):
if len(value.args) == 2:
name, msg = value.args
value.args = (msg,)
sys.__excepthook__(name, value, traceback)
else:
sys.__excepthook__(type, value, traceback)
sys.excepthook = excepthook_wrapper
(Edit: I'm not really happy with this because now 'normal' Exceptions with two arguments will get handled differently too. Possible solution, 'tag' your special Exceptions by passing "PICKLED" as a first argument and then check for that, instead of checking for the length of the args.)
And then create the Exception with two arguments, the name (__module__.__class__) and the Exception message (str(e)):
try:
try:
#print foo
raise BadExc("bad exception error message", "a")
except Exception, e:
cls = e.__class__
if hasattr(cls, '__module__'):
name = '{0}.{1}'.format(cls.__module__, cls.__name__)
else:
name = cls.__name__
raise Exception(name, str(e)), None, sys.exc_info()[2]
except Exception, f:
pass
Then this:
import cPickle
a = cPickle.dumps(f)
l = cPickle.loads(a)
print "raising error"
raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]
Prints:
raising error
Traceback (most recent call last):
File "test.py", line 18, in <module>
raise BadExc("bad exception error message", "a")
__main__.BadExc: bad exception error message

Categories

Resources