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/")
Related
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)
I am new to Python. I wanted to test if my code threw an exception. I got the code from How do you test that a Python function throws an exception?
import mymod
import unittest
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc, compulsory_argument)
Now, I also want to display a message if the exception is not thrown. How do I do that? The Python documentation does not mention it clearly. I added the message after "compulsory_argument" and it failed.
I tried the first answer with modifications and got an exception. What is my mistake here?
import unittest
def sayHelloTo(name):
print("Hello " + name)
class MyTestCase(unittest.TestCase):
def test1(self):
person = "John"
with self.assertRaises(Exception, "My insightful message"):
sayHelloTo(person)
Error:
Error
Traceback (most recent call last):
File "C:\tests\tester.py", line 9, in test1
with self.assertRaises(Exception, "My insightful message"):
AttributeError: __exit__
As of Python 3.3, assertRaises can be used as a context manager with a message:
import unittest
def sayHelloTo(name):
print("Hello " + name)
class MyTestCase(unittest.TestCase):
def test1(self):
person = "John"
with self.assertRaises(Exception, msg="My insightful message"):
sayHelloTo(person)
if __name__ == "__main__":
unittest.main()
It results in
Hello John
F
======================================================================
FAIL: test1 (__main__.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "r.py", line 10, in test1
sayHelloTo(person)
AssertionError: Exception not raised : My insightful message
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Now, I also want to display a message if the exception is not thrown. How do I do that ?
The general philosophy of unittest is for the tests to be silent when they succeed and only become verbose when they fail. Accordingly, the API provides a "msg" keyword argument for the unsuccessful case but does not offer an alternative for the successful case.
That said, another part of the philosophy works in your favor. In general, test cases internally raise an exception when a testcase fails. That means that if you want to display a message when there is a success, you just add another statement after the test:
with self.assertRaises(TypeError, msg='Oh no, I did not get a TypeError')
somecode()
logging.info('Yippee, we got a TypeError!') # Runs after a successful test
I have a test case with a helper method assertContains(super, sub). The sub arguments are a hard-coded part of the test cases. In case they're malformed, I would like my test case to abort with an error.
How do I do that? I have tried
def assertContains(super, sub):
if isinstance(super, foo): ...
elif isinstance(super, bar): ...
else: assert False, repr(sub)
However, this turns the test into a failure rather than an error.
I could raise some other exception (e.g. ValueError), but I want to explicitly state that I'm declaring the test case to be in error. I could do things like ErrorInTest = ValueError and then raise ErrorInTest(repr(sub)), but it feels kinda' icky. I feel there should be a batteries-included way of doing this, but reading the friendly manual didn't suggest anything to me.
There is an assertRaises() for aspects in class TestCase in which you want to ensure an error is raised by the to-be-tested code.
If you want to raise an error and abort testing that unit at this point (and continue with the next unit test), just raise an uncaught exception; the unit test module will catch it:
raise NotImplementedError("malformed sub: %r" % (sub,))
I don't think that there is any other API aspect available besides raising errors directly to state that a unit test case results in an error.
class PassingTest(unittest.TestCase):
def runTest(self):
self.assertTrue(True)
class FailingTest(unittest.TestCase):
def runTest(self):
self.assertTrue(False)
class ErrorTest(unittest.TestCase):
def runTest(self):
raise NotImplementedError("error")
class PassingTest2(unittest.TestCase):
def runTest(self):
self.assertTrue(True)
results in:
EF..
======================================================================
ERROR: runTest (__main__.ErrorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./t.py", line 15, in runTest
raise NotImplementedError("error")
NotImplementedError: error
======================================================================
FAIL: runTest (__main__.FailingTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./t.py", line 11, in runTest
self.assertTrue(False)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 4 tests in 0.002s
FAILED (failures=1, errors=1)
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
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.