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 just ran into the same problem when mocking
struct
.I get the error:
When trying to catch a
struct.error
raised fromstruct.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 exampleThe method I want to test has this basic pattern:
The test has this basic pattern.
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.
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:
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 topatch
replaces the target with aMagicMock
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.So what's with the
lambda
? Thenew_callable
param calls whatever we give it and replaces the target with the return of that call. If we pass ourStubException
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 withlambda
, it returns our class as we intend.Once our patching is done, the
stub_exception
object (which is literally ourStubException
class) can be raised and caught as if it were theCustomError
. 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:
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:
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:
For
requests
, you could assign exceptions individually like this:or do it for all of the
requests
exceptions like this: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.
I could reproduce the error with a minimal example:
foo.py:
Test without mocking :
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 :Immediately gives :
Here I get a first
TypeError
that 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 theexcept
clause tries to catch a mock.TL/DR: as you mock the full
requests
package, theexcept requests.exceptions.ConnectionError
clause tries to catch a mock. As the mock is not really aBaseException
, 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 patchrequests.head
. So I think that this should work :That is : only patch the
head
method with the exception as side effect.