Python mock access "real" objects while mocking a module - python

Is it possible to access "real" objects while mocking a module? What I'm trying to do is mock some function, but throw "real" exceptions, like this:
#mock.patch('my_project.requests')
def test_foo(self, mock_requests):
mock_requests.post = mock.Mock(side_effect=requests.ConnectionError())
thread = CommandThread("command", 3, 2, 0)
thread.run() #this is were I exercise requests.post
self.assert(thread.was_successful is False)
Inside my CommandThread I have a check like
try:
requests.post(url, data=data)
except (requests.ConnectionError, requests.Timeout):
self.was_successful = False
however, my test fails because the exception is not caught inside the try/except block (when I do like except Exception: it works)
The reason, I think, is simply because I mocked this "namespace" in my test case, so I actually raise my_project.requests.ConnectionError exception rather than proper, requests.ConnectionError from original package.
Is it somehow possible to access/throw "real" exceptions?

This is happening because your mock is actually overwriting the entire requests module in your code. Here is how you can debug this:
In your code, add this:
try:
requests.post('', data='')
except (requests.ConnectionError, requests.Timeout):
was_successful = False
except Exception, err:
import pdb
pdb.set_trace()
When you run the test, you will be dropped into the debugger so that you can take a look at what is happening. If we look at what you are catching, this is what we see:
(Pdb) requests.ConnectionError
<MagicMock name='requests.ConnectionError' id='4562438992'>
You are actually catching a mock ConnectionError because your mock patched out the entire requests module and you were feeding in a real requests error.
You can fix this by making your mock more specific and only overriding the post method on the requests module:
#mock.patch('my_project.requests.post')
def test_foo(self, mock_requests):
mock_requests.side_effect = requests.ConnectionError()
...

Related

How do I "post mortem" a "Caught Exception" in Python in VS.Code?

In VS.Code with the Python extension, I can break on "Uncaught Exceptions" and "Raised Exceptions".
But what if I don't want to break on all exceptions, only those that are uncaught in a certain function? (but which are caught, say by a framework like FastAPI, in a shallower frame?)
If debugging on the console, when I know there is a function that has a line causing an exception, I can change it from something like this:
def some_function():
line_causing_some_exception()
To this:
def some_function():
try:
line_causing_some_exception()
except:
import pdb
pdb.post_mortem()
# Let the framework handle the exception as usual:
raise
I can even debug graphically, instead of depending on the console, by using the excellent wdb, which is very handy for server apps, or for when I'm not launching the python code directly and have no access to its console:
def some_function():
try:
line_causing_some_exception()
except:
import wdb
wdb.post_mortem()
# Let the framework handle the exception as usual:
raise
In VS.Code, if I add a breakpoint after catching the exception, I don't have the "post mortem" traceback, only the current frame:
def some_function():
try:
line_causing_some_exception()
except:
import debugpy
debugpy.breakpoint()
# Let the framework handle the exception as usual:
raise
I couldn't find an equivalent debugpy.post_mortem() as there is for pdb and wdb.
How can I trigger a post-mortem debugging explicitly, similar to the "Uncaught Exceptions" checkbox, but for exceptions that are going to be caught on earlier frames?

In pytest, how to assert if an exception (which is inherited from a parent exception class) is raised?

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

Unable to pass assertRaises test in Python

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.

How to test Retry in Celery application in Python?

I'm trying to test if the application is retrying.
#celery.task(bind=False, default_retry_delay=30)
def convert_video(gif_url, webhook):
// doing something
VideoManager().convert(gif_url)
return
except Exception as exc:
raise convert_video.retry(exc=exc)
And I'm mocking the test
#patch('src.video_manager.VideoManager.convert')
#patch('requests.post')
def test_retry_failed_task(self, mock_video_manager, mock_requests):
mock_video_manager.return_value= {'webm':'file.webm', 'mp4':'file.mp4', 'ogv' : 'file.ogv', 'snapshot':'snapshot.png'}
mock_video_manager.side_effect = Exception('some error')
server.convert_video.retry = MagicMock()
server.convert_video('gif_url', 'http://www.company.com/webhook?attachment_id=1234')
server.convert_video.retry.assert_called_with(ANY)
And I'm getting this error
TypeError: exceptions must be old-style classes or derived from BaseException, not MagicMock
Which is obvious but I don't know how to do it otherwise to test if the method is being called.
I havn't gotten it to work with just using the built in retry so I have to use a mock with the side effect of the real Retry, this makes it possible to catch it in a test.
I've done it like this:
from celery.exceptions import Retry
from mock import MagicMock
from nose.plugins.attrib import attr
# Set it for for every task-call (or per task below with #patch)
task.retry = MagicMock(side_effect=Retry)
##patch('task.retry', MagicMock(side_effect=Retry)
def test_task(self):
with assert_raises(Retry):
task() # Note, no delay or things like that
# and the task, I don't know if it works without bind.
#Celery.task(bind=True)
def task(self):
raise self.retry()
If anyone knows how I can get rid of the extra step in mocking the Retry "exception" I'd be happy to hear it!
from mock import patch
import pytest
#patch('tasks.convert_video.retry')
#patch('tasks.VideoManager')
def test_retry_on_exception(mock_video_manger, mock_retry):
mock_video_manger.convert.side_effect = error = Exception()
with pytest.raises(Exception):
tasks.convert_video('foo', 'bar')
mock_retry.assert_called_with(exc=error)
you're also missing some stuff in your task:
#celery.task(bind=False, default_retry_delay=30)
def convert_video(gif_url, webhook):
try:
return VideoManager().convert(gif_url)
except Exception as exc:
convert_video.retry(exc=exc)
The answers here didn't help me, so I dived even deeper into celery's code and found a hack that works for me:
def test_celery_retry(monkeypatch):
# so the retry will be eager
monkeypatch.setattr(celery_app.conf, 'task_always_eager', True)
# so celery won't try to raise an error and actually retry
monkeypatch.setattr(celery.app.task.Context, 'called_directly', False)
task.delay()
For me it worked to patch celery.app.task.Task.request. This way I could also simulate later retries (eg. to test that the task is retried multiple times).
Using pytest and unittest.mock.patch() this looks like:
#mock.patch("celery.app.task.Task.request")
def test_celery_task_retry(mock_request):
# Override called_directly so that Task.retry() produces a Retry exception.
mock_request.called_directly = False
# Simulate the 42nd retry.
mock_request.retries = 42
with pytest.raises(celery.exceptions.Retry) as retry_exc:
task()
assert retry_exc.value.when > 0

How can use mock on urllib2 so that no external call gets made and I can raise an exception on read()?

I'd like to raise BadStatusLine when read gets called. How can I accomplish that with mock?
I'd assign a mock object to urllib2.open like this:
from mock import Mock
import urllib2
import httplib
mock = Mock()
mock.return_value.read.side_effect = httplib.BadStatusLine('Mocked error')
urllib2.open = mock
response = urllib2.open()
response.read()
Note:
return_value is used to access the mock object returned by open()
side_effect is used to raise the desired exception when trying to execute read()

Categories