Given this code:
def main(b):
try:
a = 2 / b
return "Hello World"
except Exception:
return "Exception"
How to test the path which handles the exception using patching?
Would it possible to make a = 2 / b as a valid target to patch?
Similar questions:
How to use Python Mock to raise an exception - but with Errno set to a given value
Mocking - How do I raise exception on the caller?
Mocking a function to raise an Exception to test an except block
Related
I am trying to test the exception block of a simple python function as follows
function_list.py
def option_check():
"""
Function to pick and return an option
"""
try:
# DELETE_FILES_ON_SUCCESS is a config value from constants class. Valid values True/False (boolean)
flag = Constants.DELETE_FILES_ON_SUCCESS
if flag:
return "remove_source_file"
else:
return "keep_source_file"
except Exception as error:
error_message = F"task option_check failed with below error {str(error)}"
raise Exception(f"Error occured: {error}") from error
How do I force and exception to unit test the exception block? Please note that what I have here in exception block is a simplified version of what I actually have. What I am looking for is a way to force an exception using unit test to test the exception scenarios.
Python version is 3.6
You could patch the Constants class and delete the attribute you access from the mock.
from unittest import mock
# replace __main__ with the package Constants is from
with mock.patch("__main__.Constants") as mock_constants:
del mock_constants.DELETE_FILES_ON_SUCCESS
option_check()
When option_check() tries to access Constants.DELETE_FILES_ON_SUCCESS, it will raise an AttributeError, allowing you to reach the except block.
This question already has answers here:
How to properly assert that an exception gets raised in pytest?
(14 answers)
Closed 2 years ago.
I'm trying to add some unit tests to make sure that the correct Error is being thrown (my function either throws KeyError or RuntimeError).
I've tried assert func(bad_param) == KeyError and assert isinstance(func(bad_param), KeyError) but neither of these are correct, how am I supposed to write unit tests for this / what's the correct way to assert that this function, when passed a bad parameter, will raise the correct Exception?
EDIT: I'm NOT using the unittest library, I'm asking about purely the assert function that comes with Python std lib (https://docs.python.org/3/reference/simple_stmts.html)
You can use the pytest.raises context manager:
with pytest.raises(ExpectedErrorType):
unit_under_test()
See pytest docs for more details.
I haven't found any example in the docs that would explain how to expect multiple exception types, but you can do that manually by inspecting the exception info object:
with pytest.raises(Exception) as exc_info:
unit_under_test()
assert issubclass(exc_info.type, (KeyError, RuntimeError))
# alternative more strict assertion
assert exc_info.type in (KeyError, RuntimeError)
I already done something like this and I used try ... except statement :
try:
# Code to test
except KeyError:
assert True
assert False
Here is a minimal working example (for pytest-like unitary test) :
def fun():
raise KeyError
def test():
try:
fun()
except KeyError:
assert True
return
assert False
You can use try except to catch exception like
def foo():
raise ValueError
rightExceptionRaised = False
try:
foo()
except ValueError:
rightExceptionRaised = True
except:
pass
assert rightExceptionRaised
EDIT: Refer to post written by #plamut
I found an answer that might be more aligned with how pytest intended to check for it!
def foo():
raise KeyError
def test_foo_error():
with pytest.raise(KeyError)
foo()
try:
context.do_something()
except ValueError:
return False
I do i test this particular code. When i use side effort e.g.
context = mock.MagicMoc()
context.do_something.side_effect = ValueError
When i use pytest.raises, the test passes but the code is not tested.
I have tried using assert but it fails
Any suggestions
I'm assuming you're wrapping the try/except code in a function that you want to test. Here are two options to test this.
1) Use a context manager to check that an exception is raised, after changing your function to re-raise the ValueError (though if you're not going to do anything with it, you might as well just not catch it in the first place):
from unittest import TestCase, mock
def do_something(c):
try:
c.do_something()
except ValueError as e:
raise e
class TestSomething(TestCase):
def test_do_something(self):
context = mock.MagicMock()
context.do_something.side_effect = ValueError
with self.assertRaises(ValueError):
do_something(context)
2) Return True in the successful control path of your function, then just check this condition in your test:
from unittest import TestCase, mock
def do_something(c):
try:
c.do_something()
return True
except ValueError as e:
return False
class TestSomething(TestCase):
def test_do_something(self):
context = mock.MagicMock()
context.do_something.side_effect = ValueError
self.assertTrue(do_something(context))
What I have done :
I have a function def get_holidays(): which raises a Timeout error. My test function test_get_holidays_raises_ioerror(): first sets requests.get.side_effect = IOError and then uses pytest.raises(IOError) to assert if that function raises an IOError.
What the issue is :
Ideally this should fail, since my actual get_holidays() does not raise an IOError. But the test passes.
Possible reason :
This might be because Timeout is inherited from the IOError class.
What I want :
Want to assert specifically if IOError is raised.
Code :
from mock import Mock
import requests
from requests import Timeout
import pytest
requests = Mock()
# Actual function to test
def get_holidays():
try:
r = requests.get('http://localhost/api/holidays')
if r.status_code == 200:
return r.json()
except Timeout:
raise Timeout
return None
# Actual function that tests the above function
def test_get_holidays_raises_ioerror():
requests.get.side_effect = IOError
with pytest.raises(IOError):
get_holidays()
pytest captures the exception in an ExceptionInfo object. You can compare the exact type after the exception.
def test_get_holidays_raises_ioerror():
requests.get.side_effect = IOError
with pytest.raises(IOError) as excinfo:
get_holidays()
assert type(excinfo.value) is IOError
So, I have the most trivial in the world example. This is my class to be tested:
# My_Class.py
class My_Class(object):
#staticmethod
def doit(name, params):
try:
raise Exception("This is my error message")
except Exception:
print("Exception: I raised Exception")
And this is the tester itself:
# test.py
import unittest
from My_Class import My_Class
class Test_MyClass(unittest.TestCase):
def setUp(self):
self.my_class = My_Class()
def test_my_class(self):
name = "Abrakadabra"
params = {}
self.assertRaises(Exception, self.my_class.doit, name, params)
And this is what I see in the console, when I'm running my test.py:
$ nosetests test.py
F
======================================================================
FAIL: test_my_class (test.Test_MyClass)
----------------------------------------------------------------------
Traceback (most recent call last):
File ....
nose.proxy.AssertionError: Exception not raised by doit
-------------------- >> begin captured stdout << ---------------------
Exception: I raised Exception
--------------------- >> end captured stdout << ----------------------
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
It is reaaly iteresting, because it is controversial. On the one hand the test says that "Exception not raised by doit", but one line below it clearly prints out a message from the Exception block. So, what I'm doing wrong here??? Thanks!
To directly answer your question, the reason why you are getting that message is because with this assertion:
self.assertRaises(Exception, self.my_class.doit, name, params)
You are testing to make sure an exception was raised. But your try/except suppresses this. If you actually remove your try/except your test will in fact pass, because now your method will raise.
Since you do not want to do this, what you should be doing instead is testing the behaviour of your method when an exception is raised. Ultimately, you want to make sure that your print method is called in your except. I have put together an example below to help understand this.
Keeping in mind what #user2357112 mentioned, which is very important to keep in mind when unittesting, here is an example to help expand on that to provide a practical use for what you are trying to do:
Let us just put together some method:
def some_method():
pass
We will now put this in to your staticmethod you defined as such:
# My_Class.py
class My_Class(object):
#staticmethod
def doit(name, params):
try:
some_method()
except Exception:
print("Exception: I raised Exception")
So now, when it comes to your unittesting, you want to test the behaviour of your method doit. With that in mind, what you will do in this case, is test that some_method will raise an exception and you will validate how your doit method behaves to that exception being raised.
At this point, I suggest taking a look at the documentation behind unittest and mock to get more familiar with what you can do with your testing, but here is an example using mock patching to test the behaviour of your code if an exception is being raised:
#patch('builtins.print')
#patch('__main__.some_method')
def test_my_class(self, m_some_method, m_print):
name = "Abrakadabra"
params = {}
# have the side_effect raise the exception when some_method is called in doit
m_some_method.side_effect = Exception()
self.my_class.doit(name, params)
# check to make sure you caught the exception by checking print was called
self.assertEqual(m_print.call_count, 1)
When you put it all together, the following is functional code that I ran on my end that you can play around with to understand what is happening:
def some_method():
pass
# My_Class.py
class My_Class(object):
#staticmethod
def doit(name, params):
try:
some_method()
except Exception:
print("Exception: I raised Exception")
# test.py
import unittest
from mock import patch
class Test_MyClass(unittest.TestCase):
def setUp(self):
self.my_class = My_Class()
#patch('builtins.print')
#patch('__main__.some_method')
def test_my_class(self, m_some_method, m_print):
name = "Abrakadabra"
params = {}
m_some_method.side_effect = Exception()
self.my_class.doit(name, params)
self.assertEqual(m_print.call_count, 1)
if __name__ == '__main__':
unittest.main()
assertRaises is an assertion about the function's visible behavior, not its internals. It asserts that the stated exception passes out of the function. Any exceptions that are handled inside the function are not assertRaises's concern.
assertRaises failed since there was actually no exception raised. Well, it was raised but handled with except inside the doit() method. The problem is here:
try:
raise Exception("This is my error message")
except Exception:
print("Exception: I raised Exception")
You are raising an exception and then catching it without re-raising. From a caller (assertRaises is the caller in your case) perspective, no errors were thrown during the function call. Re-raising an exception allows a caller to handle an exception as well. Put a raise after the print:
try:
raise Exception("This is my error message")
except Exception:
print("Exception: I raised Exception")
raise # re-raising
Also see Handling Exceptions.