Is there a way to get unittest standard library to check for multiple exceptions?
Obviously assertRaises works for a single exception: How do you test that a Python function throws an exception?
But I want to test whether at least one error is raised. This feels right, but is not correct:
with self.assertRaises(StatisticsError, ZeroDivisionError): # Test one or the other?
my_list_mean([])
Full MRE: a "mean" function may raise a ZeroDivisionError or a StatisticsError depending on the implementation. I want to assert that this raises one or the other:
from statistics import mean, StatisticsError
import unittest
def my_list_mean(lof):
# return sum(lof) / len(lof) # ZeroDivisionError
return mean(lof) # StatisticsError
class TestMultipleWaysToComputeMean(unittest.TestCase):
def test_zero_division_or_statistics_error(self):
with self.assertRaises(ZeroDivisionError):
_ = my_list_mean([])
if __name__ == "__main__": unittest.main()
Pass an actual tuple. Right now, you are passing StatisticsError as an exception and ZeroDivisionError as a callable that might produce a StatisticsError.
with self.assertRaises((StatisticsError, ZeroDivisionError)):
my_list_mean([])
From the documentation:
Test that an exception is raised when callable is called with any positional or keyword arguments that are also passed to assertRaises(). The test passes if exception is raised, is an error if another exception is raised, or fails if no exception is raised. To catch any of a group of exceptions, a tuple containing the exception classes may be passed as exception.
Related
Coming from this thread, it seems that is not possible with pytest to check if a specific exception is raised: if you mention the parent exception, the test passes as well.
I made this little example to illustrate, all three tests passed just fine. I expect only one to pass.
Could you confirm ? Should I check the excinfo ?
(Why is this a problem? The function I was testing can, in some cases, return a true TypeError exception, and I'd like my tests to detect precisely when one or the other is raised)
Thanks
import pytest
class CustomExc(TypeError):
pass
def a_random_function():
raise CustomExc
def test_random_function1():
with pytest.raises(TypeError):
a_random_function()
def test_random_function2():
with pytest.raises(CustomExc):
a_random_function()
def test_random_function3():
with pytest.raises(BaseException):
a_random_function()
CustomExc is both a TypeError and a BaseException, so this is expected behavior. The raised exception is checked for its type using isinstance, which means that all base classes will also pass.
If you want to test for a concrete exception, and not any derived exception, you have to do the check yourself. As you mentioned, you can check the exception info:
def test_random_function():
with pytest.raises(TypeError) as e:
a_random_function()
assert e.type == CustomExc
I have a function that has try/except as follows:
def func_A():
try:
# do some stuff
except Exception as e:
log.error("there was an exception %s", str(e))
I want to write a unit test for this func_A()
More importantly, I want to ensure that
No exception was caught inside A
I have try/except just for safety.
Unless there is a bug, there should be no exception thrown inside A (although it will be caught with try/except) and that's what I want to validate with my unit test.
What is the best way for unit test to catch the case where there was an exception thrown and caught?
If you really need this, one possible way is to mock out the log.error object. After invoking the func_A function, you can make an assertion that your mock wasn't called.
Note that you should not catch exceptions at all if you don't intend to actually handle them. For proper test coverage, you should provide 2 tests here - one which checks each branching of the try/except.
Another possible solution is to split implementation into two functions:
Function foo() with logic without try statement. This way you can make sure that no exception is thrown in your implementation.
safe_foo() which wraps foo() into try statement. Then you can mock foo() to simulate throwing an exception by it and make sure every exception is caught.
Drawback is that either foo() will be part of a public interface or you will write tests for a private function.
You can have one variable which will track function executed properly or ended in exception.
def func_A():
function_state = True
try:
# do some stuff
except Exception as e:
log.error("there was an exception %s", str(e))
function_state = False
return function_state
Use assertTrue to validate function_state.
Option 1: Don't. This is testing an implementation detail. Try to write your test suite so that you very the function does everything you need it to do. If it does what you want with the inputs you want, you're good.
Option 2: You can modify the function to take a logger as a parameter. Then in the test case, pass in a mock object and check that the logging method gets called.
I have an outer function that calls an inner function by passing the arguments along. Is it possible to test that both functions throw the same exception/error without knowing the exact error type?
I'm looking for something like:
def test_invalidInput_throwsSameError(self):
arg = 'invalidarg'
self.assertRaisesSameError(
innerFunction(arg),
outerFunction(arg)
)
Assuming you're using unittest (and python2.7 or newer) and that you're not doing something pathological like raising old-style class instances as errors, you can get the exception from the error context if you use assertRaises as a context manager.
with self.assertRaises(Exception) as err_context1:
innerFunction(arg)
with self.assertRaises(Exception) as err_context2:
outerFunction(arg)
# Or some other measure of "sameness"
self.assertEqual(
type(err_context1.exception),
type(err_context2.exception))
First call the first function and capture what exception it raises:
try:
innerfunction(arg)
except Exception as e:
pass
else:
e = None
Then assert that the other function raises the same exception:
self.assertRaises(e, outerfunction, arg)
I believe this will do what you want; just add it as another method of your TestCase class:
def assertRaisesSameError(self, *funcs, bases=(Exception,)):
exceptions = []
for func in funcs:
with self.assertRaises(base) as error_context:
func()
exceptions.append(error_context.exception)
for exc in exceptions:
self.assertEqual(type(exc), type(exc[-1]))
I haven't tested this, but it should work. For simplicity this only takes functions with no arguments, so if your function does have arguments, you'll have to make a wrapper (even just with a lambda). It would not be hard at all to expand this to allow for passing in the *args and **kwargs.
Let me know of any changes anyone thinks should be made.
I've searched for documentation, but couldn't find any. There were a couple that didn't explain much.
Can someone explain to me Nose's
assert_raises(what should I put here?)
function and how to use it?
While the accepted answer is correct, I think there is a better use to assert_raises method.
If you simply want to assert that an exception occurs, it's probably simpler and cleaner to use #raises syntax.
#raises(HTTPError)
def test_exception_is_raised:
call_your_method(p1, p2)
However, assume you want to do bit more with the raised exception, for example: we need to assert that raised HTTPError is of type 401: Unauthorized, instead of 500: Server Error.
In such a situation above syntax is not that helpful, we should use the assert_raises but in a different way.
If we do not pass it a callable as the second parameter assert_raises will return back a context which we can use to further test the exception details.
def test_exception_is_raised:
with assert_raises(HTTPError) as cm:
call_your_method(p1, p2)
ex = cm.exception # raised exception is available through exception property of context
ok_(ex.code == 401, 'HTTPError should be Unauthorized!')
The assert_raises() function tests to make sure a function call raises a specified exception when presented with certain parameters.
For example, if you had a function add that adds two numbers, it should probably raise a TypeError when you pass it, say, an integer and a string. So:
from nose.tools import assert_raises
def add(x, y):
return x + y
assert_raises(TypeError, add, 2, "0")
The first argument is the exception type you expect. The second is the function to call. The rest of the arguments will be passed to the function (in this case, they will become x and y inside the function).
If the expected exception is raised by the function, the assertion passes.
This question already has answers here:
Pass a Python unittest if an exception isn't raised
(3 answers)
Closed 2 years ago.
I'm trying to test if some function DOES NOT throws any exception, using the unittest module.
Is this possible? I only know about the assertRaises method, but this one just test for thrown exceptions, and a None argument won't work for the Exception type argument...
Any ideas?
So just call the function and if it raise an exception it will be reported as an ERROR not a failure which mean your unittest runner will stop (no way to continue it), if you want to report it as a Failure you can do like this:
try:
somefunction()
except:
self.fail("....")
If an exception occurs the test will be marked as failed, so I don't see why you need to actually assert anything. If you really want to, you can always just do it yourself:
try:
foo()
except:
raise AssertionError('should not raise exception')
I ran into same problem for my unit tests. mouad & Michael are correct, but for cleaner way to handle assertion for exception with consistent method.
class TestCase(unittest.TestCase):
def assertNoRaise(self, callableObj):
try:
callableObj()
except:
raise AssertionError('shouldn\'t raise an exception')
def assertAnyRaise(self, callableObj):
try:
callableObj()
except:
return
raise AssertionError('should raise an exception')
or seperate above class from inheritance, and do mixin with unittest.TestCase. either way would work
and a use case
class my_test(TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_assertion(self):
self.assertAnyRaise(lambda: 1+'a')
self.assertNoRaise(lambda: 1+1)
please excuse my bad english.