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

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()

Related

Writing a unit test for Python REST API function

I'm currently learning Python REST API (side project). I've been reading a lot of tutorials from RealPython, Python Requests documentation, etc. I found this post on how to write try/except properly in Python (Correct way to try/except using Python requests module?). One thing that still confuses me though is how to create a unit test for a function like this since it is not returning anything. Any help?
def google_do_something(blahblah):
url='http://www.google.com/' + blahblah
try:
r = requests.get(url,timeout=3)
r.raise_for_status()
except requests.exceptions.HTTPError as errh:
print (errh)
except requests.exceptions.ConnectionError as errc:
print (errc)
except requests.exceptions.Timeout as errt:
print (errt)
except requests.exceptions.RequestException as err:
print (err)
I could think of this but I don't know what to assert with.
def test_google_do_something():
g = google_do_something('blahblah')
# assert??
There are several unit test frameworks available in Python. Try/except blocks are good for error handling, but you still need a separate unit test around the call if you want to unit test it.
You do have something you can test, you can just return it and test that in your unit test.
Example Unit test using unittest:
import unittest
import requests
class RestCalls():
def google_do_something(blahblah):
url= blahblah
try:
r = requests.get(url,timeout=1)
r.raise_for_status()
return r.status_code
except requests.exceptions.Timeout as errt:
print (errt)
raise
except requests.exceptions.HTTPError as errh:
print (errh)
raise
except requests.exceptions.ConnectionError as errc:
print (errc)
raise
except requests.exceptions.RequestException as err:
print (err)
raise
class TestRESTMethods(unittest.TestCase):
def test_valid_url(self):
self.assertEqual(200,RestCalls.google_do_something('http://www.google.com/search'))
def test_exception(self):
self.assertRaises(requests.exceptions.Timeout,RestCalls.google_do_something,'http://localhost:28989')
if __name__ == '__main__':
unittest.main()
Executing should show (made some edits to this post, updated output included at bottom of post):
> python .\Tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.192s
OK
If you asserted a different response code from your request, it would fail (the request is just returning http response codes):
python .\Tests.py
F
======================================================================
FAIL: test_upper (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File ".\Tests.py", line 25, in test_upper
self.assertEqual(404,RestCalls.google_do_something('search'))
AssertionError: 404 != 200
----------------------------------------------------------------------
Ran 1 test in 0.245s
FAILED (failures=1)
Which is expected.
Edit: Included exception testing. You can test these by just including raise in the except block, which will show this after running:
> python .\Tests.py
HTTPConnectionPool(host='localhost', port=28989): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x03688598>, 'Connection to localhost timed out. (connect timeout=1)'))
..
----------------------------------------------------------------------
Ran 2 tests in 2.216s
OK
References:
Unit tests in Python
https://docs.python.org/3/library/unittest.html
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
I am not sure that your approach is such a good idea (just printing something in case of an error) but you could mock the print function to see if it was really called (and with what arguments):
https://docs.python.org/3/library/unittest.mock.html?highlight=mock#module-unittest.mock
Edit:
Working with mocks is a bit tricky as far as I remember. You would have to mock the print function in the current module. Perhaps something like this (not tested ...):
from unittest.mock import patch
from unittest import TestCase
class TestGoogleDoSomething(TestCase)
#patch("nameOfYourModule.print")
def test_google_do_something(self, print_mock): # the decorator will pass the mock object into the function
g = google_do_something('blahblah')
print_mock.assert_called_with("your error message here ...")
It seems that you are using print instead of all exception handlers. I don’t think that is a good practice. From my perspective, I prefer to raise those Exceptions out again if not sure how to deal with them right now.
With that said, when any error occurs, an exception will be thrown out; if there’re no exceptions, that means this function work well. Therefore you can design your unit test cases base on that.

Exception not being caught in try-except block during unit test

I have this in my code:
import api
def do_something():
try:
api = api.Api()
api.call()
except ParseException as e:
logger.exception('Error occurred')
raise ValidationError(detail=e.message)
Basically it calls an API and re-raises the exception with another type.
My test checks the case when the exception is thrown:
#patch('code.api')
def test_exception(self, api_mock):
api_mock.Api.side_effect = ParseException('General Error')
self.assertRaises(
ValidationError,
do_something
)
api_mock.Api.assert_called_once()
However my test fails because ParseException gets thrown and not ValidationError. What is going on?
Note the #patch('code.api') line. This says patch EVERYTHING in code.api. The ParseException is probably in the api module too and thus patched too. If you debug your code you'll see that type(ParseException) is not an instance of Exception but an instance of MagicMock.
I just spent an hour banging my head on the desk, hope this helps someone.

unittest - assertRaises return an error instead of passing

I've written a piece of code that uses a config file (in JSON-format)
def test_read_config_file(self):
self.assertRaises(ValueError, self.read_config_file('no_json.txt')
The original function looks like this:
def read_config_file(file_name)
config_data = None
try:
with open(file_name, 'r') as infile:
config_data = json.load(infile)
except ValueError as err:
LOGGER.error(str(err))
return config_data
When i run my testcase i get this:
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
2016-07-27 12:41:09,616 ERROR read_config_file(158) No JSON object could be decoded
no_json.txt just contains "Hi". Why am i getting 4 error here?
Thanks,
You are not using the unittest library correctly. When you write this:
def test_read_config_file(self):
self.assertRaises(ValueError, self.read_config_file('no_json.txt'))
# Btw. there was a missing closing `)`
the self.read_config_file() method is executed before the self.assertRaises. If it fails the self.assertRaises will never be called. Instead the exception bubbles up until something else catches it.
You want the self.assertRaises method to execute the self.read_config_file method. Because then and only then it can catch a potential ValueError. To do this you have two options:
Pass the method to test and the arguments separately:
self.assertRaises(ValueError, self.read_config_file, "no_json.txt")
Like this self.assertRaises will call the function you passed into it with the arguments specified. Then the exception occurs inside self.assertRaises where it can be caught and let the test succeed.
The second option is to use a context manager:
def test_read_config_file(self):
with self.assertRaises(ValueError):
self.read_config_file("no_json.txt")
Like this the exception will happen inside the with statement. In the cleanup step of the context manager the presence of such an exception can then again let the test succeed.
EDIT:
From your edit i can see that you already handle the ValueError in your self.read_config_file method. So any self.assertRaises approach will fail anyway. Either let the self.read_config_file raise the error or change your test.
The problem is that your function catches the ValueError exception that json.load raises, performs the logging, then simply goes on to return the value of config_data. Your test asserts that the function should raise an exception, but there is no code to ensure that the function does that.
The easiest way to fix this would be to modify the code by adding a raise statement to ensure that the ValueError is re-raised to be trapped by the assertRaises call:
def read_config_file(file_name)
config_data = None
try:
with open(file_name, 'r') as infile:
config_data = json.load(infile)
except ValueError as err:
LOGGER.error(str(err))
raise
return config_data

How to cover the except portion of a try-except in a python pytest unit test

I'm new to Python. I need to unit test the except part of a try-except statement in python. I'm using pytest. The problem is that I don't know how to force the try part to raise an exception. Here is my code:
try:
if master_bill_to is False:
master.update(
dbsession,
company_id=master.company_id,
)
except Exception as e:
dbsession.rollback()
raise Conflict(e.message)
The master.update method is called to make an update to the database. But how do I mock this code so that it somehow raises an exception in the try portion?
I'm trying to use monkeypatch with this code. The master object is an instance of the BillTo class so I'm thinking of putting that as the first parameter to monkeypatch.setattr.
def test_create_bill_to_fails_when_master_update_fails(dbsession, invoice_group1, company1,
monkeypatch):
def raise_flush_error():
raise FlushError
context = TestContext()
monkeypatch.setattr(BillTo, 'update', raise_flush_error)
with pytest.raises(FlushError):
create_bill_to(
context,
dbsession=dbsession,
invoice_group_id=invoice_group1.id,
company_id=company1.id,
)
But for some reason, the error is not raised.
Use mock library and side_effect to throw Exception during test case
Mock master and raise an exception in the update method.
Ok, I figured it out. I learned that you must pass parameters to the method called by monkeypatch. These parameters must match the signature of the method being replaced or mocked. Actually I also renamed the method with a prefix fake_ to denote the mocking. Here is what I did:
#staticmethod
def fake_update_flush_error(dbsession, company_id=None, address_id=None, proportion=None,
company_name=None, receiver_name=None, invoice_delivery_method=None,
invoice_delivery_text=None, master_bill_to=False):
raise FlushError
def test_create_bill_to_fails_when_master_update_fails(dbsession, invoice_group1, company1,
bill_to1, monkeypatch):
context = TestContext()
monkeypatch.setattr(BillTo, 'update', fake_update_flush_error)
with pytest.raises(Conflict):
create_bill_to(
context,
dbsession=dbsession,
invoice_group_id=invoice_group1.id,
company_id=company1.id,
address_id=None,
...
)
The BillTo.update method needed all those parameters.

Catching an exceptions in __enter__ in the calling code in Python

Is there a way I can catch exceptions in the __enter__ method of a context manager without wrapping the whole with block inside a try?
class TstContx(object):
def __enter__(self):
raise Exception("I'd like to catch this exception")
def __exit__(self, e_typ, e_val, trcbak):
pass
with TstContx():
raise Exception("I don't want to catch this exception")
pass
I know that I can catch the exception within __enter__() itself, but can I access that error from the function that contains the with statement?
On the surface the question Catching exception in context manager __enter__() seems to be the same thing but that question is actually about making sure that __exit__ gets called, not with treating the __enter__ code differently from the block that the with statement encloses.
...evidently the motivation should be clearer. The with statement is setting up some logging for a fully automated process. If the program fails before the logging is set up, then I can't rely on the logging to notify me, so I have to do something special. And I'd rather achieve the effect without having to add more indentation, like this:
try:
with TstContx():
try:
print "Do something"
except Exception:
print "Here's where I would handle exception generated within the body of the with statement"
except Exception:
print "Here's where I'd handle an exception that occurs in __enter__ (and I suppose also __exit__)"
Another downside to using two try blocks is that the code that handles the exception in __enter__ comes after the code that handles exception in the subsequent body of the with block.
You can catch the exception using try/except inside of __enter__, then save the exception instance as an instance variable of the TstContx class, allowing you to access it inside of the with block:
class TstContx(object):
def __enter__(self):
self.exc = None
try:
raise Exception("I'd like to catch this exception")
except Exception as e:
self.exc = e
return self
def __exit__(self, e_typ, e_val, trcbak):
pass
with TstContx() as tst:
if tst.exc:
print("We caught an exception: '%s'" % tst.exc)
raise Exception("I don't want to catch this exception")
Output:
We caught an exception: 'I'd like to catch this exception'.
Traceback (most recent call last):
File "./torn.py", line 20, in <module>
raise Exception("I don't want to catch this exception")
Exception: I don't want to catch this exception
Not sure why you'd want to do this, though....
You can use contextlib.ExitStack as outlined in this doc example in order to check for __enter__ errors separately:
from contextlib import ExitStack
stack = ExitStack()
try:
stack.enter_context(TstContx())
except Exception: # `__enter__` produced an exception.
pass
else:
with stack:
... # Here goes the body of the `with`.

Categories

Resources