How to test Exception in signal? - python

I have a signal:
create_flat_referral = django.dispatch.Signal(providing_args=["request", "user"])
And I have a receiver:
#receiver(create_flat_referral, sender=None)
def save_flat_referral(sender, request, user, **kwargs):
referral_link = request.POST.get('referral', None)
if not referral_link:
referral_link = request.GET.get('ref', None)
try:
if referral_link:
link = Link.objects.get(token = referral_link)
referral = FlatReferral.objects.create(
referrer = link.user,
referred = user
)
except Link.DoesNotExist:
logger.exception('...')
except ValidationError:
logger.exception('...')
I want to cover all the exceptions with the tests.
In addition to what I want to get assertRaises, I also want to get assertRaisesMessage
What's the best way to do this?
If I'm trying to do something like this:
self.assertRaises(
ValidationError,
lambda: signals.create_flat_referral.send(
sender = None,
request = post_request,
user = self.referred
)
)
That I fail the test:
AssertionError: ValidationError not raised by lambda

AssertionError: ValidationError not raised by lambda
The definition where callable is a func name, not a func() call
`assertRaises(exception, callable, *args, **kwds)`
e.g, this will work as cal_exp is func name
self.assertRaises(OverflowError, MathOverFlow().cal_exp, 2)
This will not work, as cal_exp(2) is not valid for assert
self.assertRaises(OverflowError, MathOverFlow().cal_exp(2))
I personally prefer this way:
d = {'sender': None, 'request': post_request, 'user': self.referred}
with self.assertRaises(ValidationError):
signals.create_flat_referral.send(**d)
OR a func name with **kwargs, not func(**kwargs)
self.assertRaises(ValidationError, signals.create_flat_referral.send, **d)
I cannot find a place for lamda, or at least it made things more complicated than necessary here.

You need to test your signal handler itself - not Django's signal framework (which is thoroughly tested by Django itself). So you don't need to go through all the trouble of triggering an actual signal - you can just call your function directly with simulated parameters, and check that it handles them correctly - something like this:
from django.test import TestCase, RequestFactory
class MySignalTestCase(TestCase):
def test_link_does_not_exist(self):
# Create a dummy request that you can give to your signal handler
factory = RequestFactory()
request = factory.post('/') # Pass appropriate params here if needed
with self.assertRaises(ValidationError):
# sender isn't used, hence you can just pass None there.
save_flat_referral(sender=None, request=request)

Related

pytest: How to force raising Exceptions during unit-testing?

In my python code, I am expecting exceptions could possibly be raised after calling method requests.Session.request(), for example these:
requests.exceptions.ConnectTimeout
requests.exceptions.ReadTimeout
requests.exceptions.Timeout
When any of these expected exceptions are raised, I handle them appropriately, for example possibly a retry situation.
My question, I am using py.test for unit testing, and I purposely want to inject raising exceptions from specific parts of my code. For example, the function that calls requests.Session.request(), instead of returning a valid requests.Response, it raises a requests.exception.
What I want to make sure that my code successfully handles expected and unexpected exceptions coming from other packages, which include those exceptions from requests.
Maybe... Is there a #decorator that I could add to the aforementioned function to raise exceptions upon request during unit testing?
Suggestions for doing exceptions injections for unit testing? (proper phrasing of my question would be greatly appreciated.)
Thanks for the responses!!!
Here is the entire singleton class that creates requests.Session and calls requests.Session.request():
class MyRequest(metaclass=Singleton):
def __init__(self, retry_tries=3, retry_backoff=0.1, retry_codes=None):
self.session = requests.session()
if retry_codes is None:
retry_codes = set(REQUEST_RETRY_HTTP_STATUS_CODES)
self.session.mount(
'http',
HTTPAdapter(
max_retries=Retry(
total=retry_tries,
backoff_factor=retry_backoff,
status_forcelist=retry_codes,
),
),
)
def request(self, request_method, request_url, **kwargs):
try:
return self.session.request(method=request_method, url=request_url, **kwargs)
except Exception as ex:
log.warning(
"Session Request: Failed: {}".format(get_exception_message(ex)),
extra={
'request_method': request_method,
'request_url': request_url
}
)
raise
You can make use of py.test raises, check it here: http://doc.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions
Taking into account your code you could do something along the lines of the following:
from requests.exceptions import ConnectTimeout, ReadTimeout, Timeout
from unittest.mock import patch
import pytest
class TestRequestService:
#patch('path_to_module.MyRequest')
def test_custom_request(self, my_request_mock):
my_request_mock.request.side_effect = ConnectTimeout
with pytest.raises(ConnectTimeout):
my_request_mock.request(Mock(), Mock())
Moreover, you could make use of pytest.parametrize(http://doc.pytest.org/en/latest/parametrize.html) as well:
from requests.exceptions import ConnectTimeout, ReadTimeout, Timeout
from unittest.mock import patch
import pytest
class TestRequestService:
#pytest.mark.parametrize("expected_exception", [ConnectTimeout, ReadTimeout, Timeout])
#patch('path_to_module.MyRequest')
def test_custom_request(self, my_request_mock, expected_exception):
my_request_mock.request.side_effect = expected_exception
with pytest.raises(expected_exception):
my_request_mock.request(Mock(), Mock())
Here you can find some more examples about parametrize: http://layer0.authentise.com/pytest-and-parametrization.html
In my application I am catching exception requests.exceptions.ConnectionError
and returning message which is in expected variable below.
So the test looks like this:
import pytest
import requests
expected = {'error': 'cant connect to given url'}
class MockConnectionError:
def __init__(self, *args, **kwargs):
raise requests.exceptions.ConnectionError
def test_project_method(monkeypatch):
monkeypatch.setattr("requests.get", MockConnectionError)
response = project_method('http://some.url.com/')
assert response == expected
Patching, mocking and dependecy-injection are techniques to inject fake objects. Patching is sometimes hard to do right, on the other hand dependency injection requires that have to change the code you want to test.
This is just a simple example how to use dependency-injection. First the code we want to test:
import requests
...
def fetch_data(url, get=requests.get):
return get(url).json()
# this is how we use fetch_data in productive code:
answer = fetch_data("www.google.com?" + term)
And this is then the test:
import pytest
def test_fetch():
def get_with_timeout(url):
raise ConnectTimeout("message")
with pytest.raises(ConnectTimeout) as e:
# and now we inject the fake get method:
fetch_data("https://google.com", get=get_with_timeout)
assert e.value == "message"
In your example above, the mocking technique would be as follows:
def test_exception():
class TimeoutSessionMock:
def get(self, *args, **kwargs):
raise ConnectTimeout("message")
mr = MyRequest()
mr.session = TimeoutSessionMock()
with pytest.raises(ConnectTimeout) as e:
mr.request("get", "http://google.com")
assert e.value == "message"

Any way to pass parameters into pytest fixture?

I am not talking about the Parameterizing a fixture feature that allows a fixture to be run multiple times for a hard-coded set of parameters.
I have a LOT of tests that follow a pattern like:
httpcode = 401 # this is different per call
message = 'some message' # this is different per call
url = 'some url' # this is different per call
mock_req = mock.MagicMock(spec_set=urllib2.Request)
with mock.patch('package.module.urllib2.urlopen', autospec=True) as mock_urlopen, \
mock.patch('package.module.urllib2.Request', autospec=True) as mock_request:
mock_request.return_value = mock_req
mock_urlopen.side_effect = urllib2.HTTPError(url, httpcode, message, {}, None)
connection = MyClass()
with pytest.raises(MyException):
connection.some_function() # this changes
Essentially, I have a class that's an API client, and includes custom, meaningful exceptions that wrap urllib2 errors in something API-specific. So, I have this one pattern - patching some methods, and setting side effects on one of them. I use it in probably a dozen different tests, and the only differences are the three variables which are used in part of the side_effect, and the method of MyClass() that I call.
Is there any way to make this a pytest fixture and pass in these variables?
You can use indirect fixture parametrization
http://pytest.org/latest/example/parametrize.html#deferring-the-setup-of-parametrized-resources
#pytest.fixture()
def your_fixture(request):
httpcode, message, url = request.param
mock_req = mock.MagicMock(spec_set=urllib2.Request)
with mock.patch('package.module.urllib2.urlopen', autospec=True) as mock_urlopen, \
mock.patch('package.module.urllib2.Request', autospec=True) as mock_request:
mock_request.return_value = mock_req
mock_urlopen.side_effect = urllib2.HTTPError(url, httpcode, message, {}, None)
connection = MyClass()
with pytest.raises(MyException):
connection.some_function() # this changes
#pytest.mark.parametrize('your_fixture', [
(403, 'some message', 'some url')
], indirect=True)
def test(your_fixture):
...
and your_fixture will run before test with desired params
I've done a bunch more research on this since posting my question, and the best I can come up with is:
Fixtures don't work this way. Just use a regular function, i.e.:
def my_fixture(httpcode, message, url):
mock_req = mock.MagicMock(spec_set=urllib2.Request)
with mock.patch('package.module.urllib2.urlopen', autospec=True) as mock_urlopen, \
mock.patch('package.module.urllib2.Request', autospec=True) as mock_request:
mock_request.return_value = mock_req
mock_urlopen.side_effect = urllib2.HTTPError(url, httpcode, message, {}, None)
connection = MyClass()
return (connection, mock_request, mock_urlopen)
def test_something():
connection, mock_req, mock_urlopen = my_fixture(401, 'some message', 'some url')
with pytest.raises(MyException):
connection.some_function() # this changes
How to pass parameters into a fixture?
Unpack that idea for a moment: you're asking for a fixture, which is a function, which reacts to parameters. So, return a function, which reacts to parameters:
#pytest.fixture
def get_named_service():
def _get_named_service(name):
result = do_something_with_name(name)
return result
return _get_named_service
Thus, in the test, you can provide the parameters to the function:
def test_stuff(get_named_service):
awesome_service = get_named_service('awesome')
terrible_service = get_named_service('terrible')
# Now you can do stuff with both services.
This is documented as a factory pattern:
https://docs.pytest.org/en/latest/how-to/fixtures.html#factories-as-fixtures
Which, as the OP found, is just a function, but with the advantage of being inside the conftest where all the other common utils and setup/teardown code resides; plus self-documenting the dependencies of the test.
I know this is old, but maybe it helps someone who stumbles on this again
#pytest.fixture
def data_patcher(request):
def get_output_test_data(filename, as_of_date=None):
# a bunch of stuff to configure output
return output
def teardown():
pass
request.addfinalizer(teardown)
return get_output_test_data
and then, inside the function:
with patch('function to patch', new=data_patcher):
Some trick with pytest.mark and we have a fixture with arguments.
from allure import attach
from pytest import fixture, mark
def get_precondition_params(request_fixture, fixture_function_name: str):
precondition_params = request_fixture.keywords.get("preconditions_params")
result = precondition_params.args[0].pop(fixture_function_name) if precondition_params is not None else None
return result
#fixture(scope="function")
def setup_fixture_1(request):
params = get_precondition_params(request, "setup_fixture_1")
return params
#mark.preconditions_params(
{
"setup_fixture_1": {
"param_1": "param_1 value for setup_fixture_1",
"param_2": "param_2 value for setup_fixture_1"
},
}
)
def test_function(setup_fixture_1):
attach(str(setup_fixture_1), "setup_fixture_1 value")
Now we can use one fixture code, parametrize it with mark, and do anything with params inside fixture. And fixture will be executed as precondition (how it must be), not as step (like it will be if we return function from fixture).

Strange behaviour mock’s side_effect function

I have this piece of code:
import postie
def send_mail(self, outbox):
try:
postie.postmail(self.dbname, outbox)
except postie.EmailDeliveryException:
self.logger.error('Fail to send mail’)
return False
return True
And I want to test the case a postie.EmailDeliveryException is raised.
So I mock out postie.postmail and put the exception as aside effect of its call:
import postie
#patch('postie.postmail')
def test_send_mail_False(self, postie_stub):
''' Mail send failed '''
postie_stub.return_value = None
postie_stub.side_effect = postie.EmailDeliveryException
op = OutboxProcessor('db name', None, None)
self.assertFalse(op.send_mail(Outbox()))
The above results in:
test_send_mail_False (test_outbox_processor.OutboxProcessorTestCase)
Mail send failed ... No handlers could be found for logger "outbox"
ok
Now I want to mock out the logger and check that the error function is also called in case of ‘EmailDeliveryException’. So I go:
#patch('postie.postmail')
#patch.object(Logger, 'error')
def test_send_mail_False(self, postie_stub, logger_stub):
''' Mail sending failed '''
postie_stub.return_value = None
postie_stub.side_effect = postie.EmailDeliveryException
logger_stub.return_value = None
op = OutboxProcessor('db name', None, None)
self.assertFalse(op.send_mail(Outbox(), None))
logger_stub.assert_called()
The result will be:
FAIL: test_send_mail_False (test_outbox_processor.OutboxProcessorTestCase)
Mail sending failed
AssertionError: True is not false
So it looks like the assertFalse does no longer succeed, (probably the exception is no longer raised). Anyone has any idea if anything interferes with my side_effect here? Thank you in advance!
You have incorrect order of patch decorators (or stub arguments). Here's an explanation from mock docs:
When you nest patch decorators the mocks are passed in to the
decorated function in the same order they applied (the normal python
order that decorators are applied). This means from the bottom up...
So it should be:
#patch.object(Logger, 'error')
#patch('postie.postmail')
def test_send_mail_False(self, postie_stub, logger_stub):
''' Mail sending failed '''

How do I return HTTP error code without default template in Tornado?

I am currently using the following to raise a HTTP bad request:
raise tornado.web.HTTPError(400)
which returns a html output:
<html><title>400: Bad Request</title><body>400: Bad Request</body></html>
Is it possible to return just the HTTP response code with a custom body?
You may simulate RequestHandler.send_error method:
class MyHandler(tornado.web.RequestHandler):
def get(self):
self.clear()
self.set_status(400)
self.finish("<html><body>My custom body</body></html>")
Tornado calls RequestHandler.write_error to output errors, so an alternative to VisioN's approach would be override it as suggested by the Tornado docs. The advantage to this approach is that it will allow you to raise HTTPError as before.
The source for RequestHandler.write_error is here. Below you can see an example of a simple modification of write_error that will change the set the status code and change the output if you provide a reason in kwargs.
def write_error(self, status_code, **kwargs):
if self.settings.get("serve_traceback") and "exc_info" in kwargs:
# in debug mode, try to send a traceback
self.set_header('Content-Type', 'text/plain')
for line in traceback.format_exception(*kwargs["exc_info"]):
self.write(line)
self.finish()
else:
self.set_status(status_code)
if kwargs['reason']:
self.finish(kwargs['reason'])
else:
self.finish("<html><title>%(code)d: %(message)s</title>"
"<body>%(code)d: %(message)s</body></html>" % {
"code": status_code,
"message": self._reason,
})
It's better to use the standard interface and define your custom message on the HTTPError.
raise tornado.web.HTTPError(status_code=code, log_message=custom_msg)
You can then parse the error in your RequestHandler and check for the message:
class CustomHandler(tornado.web.RequestHandler):
def write_error(self, status_code, **kwargs):
err_cls, err, traceback = kwargs['exc_info']
if err.log_message and err.log_message.startswith(custom_msg):
self.write("<html><body><h1>Here be dragons</h1></body></html>")
def write_error(self, status_code, **kwargs):
#Function to display custom error page defined in the handler.
#Over written from base handler.
data = {}
data['code'] = status_code
data['message'] = httplib.responses[status_code]
# your other conditions here to create data dict
self.write(TEMPLATES.load('error.html').generate(data=data))
when ever self.send_error() call is initiated write_error() function is called by the request handler. So you can create your custom error data dict here and render it to your custom error page.
http.responses[status_code] returns the error code text like "page not found" based on the status code.
Also you can override get_error_html method in your handler.
For example:
import tornado.web
class CustomHandler(tornado.web.RequestHandler):
def get_error_html(self, status_code, **kwargs);
self.write("<html><body><h1>404!</h1></body></html>")
...
def get(self):
...
This exchange clarifies some of the approaches suggested here, and discounts the reason keyword (which I was thinking about trying).
Q: (by mrtn)
"I want to use raise tornado.web.HTTPError(400, reason='invalid request') to pass a custom reason to the error response, and I hope to do this by overriding the write_error (self, status_code, **kwargs) method.
"But it seems that I can only access self._reason inside write_error, which is not what I want. I also tried kwargs['reason'] but that does not exist."
A: (by Tornado lead developer #bendarnell)
"The exception that exposed the error is available to write_error as an exc_info triple in the keyword arguments. You can access the reason field with something like this:
if "exc_info" in kwargs:
e = kwargs["exc_info"][1]
if isinstance(e, tornado.web.HTTPError):
reason = e.reason
"But note that the reason field is essentially deprecated (it is not present in HTTP/2), so it's probably not the best way to do whatever you're trying to do here (HTTPError's log_message field is a little better, but still not ideal). Just raise your own exception instead of using HTTPError; your write_error override can use self.set_status(400) when it sees the right kind of exception."
For json error response i use follow template:
Request handler:
import json
from tornado.web import RequestHandler
from src.lib.errors import HTTPBadRequest
class JsonHandler(RequestHandler):
def prepare(self):
content_type = ''
if "Content-Type" in self.request.headers:
content_type = self.request.headers['Content-Type']
if content_type == 'application/json':
try:
self.request.body = json.loads(self.request.body.decode('utf-8'))
except ValueError:
raise HTTPBadRequest
def write_error(self, *args, **kwargs):
err_cls, err, traceback = kwargs['exc_info']
self.set_status(err.status_code)
if err.description:
self.write_json(err.description)
self.finish()
def set_default_headers(self):
self.set_header('Content-Type', 'application/json')
def write_json(self, response):
self.write(json.dumps(response))
Errors handler:
from typing import Any
from tornado import httputil
class BaseHTTPError(Exception):
def __init__(
self, status_code: int = 500, description=None, *args: Any, **kwargs: Any
) -> None:
if description is None:
description = {}
self.status_code = status_code
self.description = description
self.args = args
self.kwargs = kwargs
def __str__(self) -> str:
message = "HTTP %d: %s" % (
self.status_code,
httputil.responses.get(self.status_code, "Unknown"),
)
return message
class HTTPBadRequest(BaseHTTPError):
def __init__(self, *args, **kwargs):
super().__init__(status_code=400, description={"error": "Bad Request"}, *args, **kwargs)

How do I mock a django signal handler?

I have a signal_handler connected through a decorator, something like this very simple one:
#receiver(post_save, sender=User,
dispatch_uid='myfile.signal_handler_post_save_user')
def signal_handler_post_save_user(sender, *args, **kwargs):
# do stuff
What I want to do is to mock it with the mock library http://www.voidspace.org.uk/python/mock/ in a test, to check how many times django calls it. My code at the moment is something like:
def test_cache():
with mock.patch('myapp.myfile.signal_handler_post_save_user') as mocked_handler:
# do stuff that will call the post_save of User
self.assert_equal(mocked_handler.call_count, 1)
The problem here is that the original signal handler is called even if mocked, most likely because the #receiver decorator is storing a copy of the signal handler somewhere, so I'm mocking the wrong code.
So the question: how do I mock my signal handler to make my test work?
Note that if I change my signal handler to:
def _support_function(*args, **kwargs):
# do stuff
#receiver(post_save, sender=User,
dispatch_uid='myfile.signal_handler_post_save_user')
def signal_handler_post_save_user(sender, *args, **kwargs):
_support_function(*args, **kwargs)
and I mock _support_function instead, everything works as expected.
Possibly a better idea is to mock out the functionality inside the signal handler rather than the handler itself. Using the OP's code:
#receiver(post_save, sender=User, dispatch_uid='myfile.signal_handler_post_save_user')
def signal_handler_post_save_user(sender, *args, **kwargs):
do_stuff() # <-- mock this
def do_stuff():
... do stuff in here
Then mock do_stuff:
with mock.patch('myapp.myfile.do_stuff') as mocked_handler:
self.assert_equal(mocked_handler.call_count, 1)
So, I ended up with a kind-of solution: mocking a signal handler simply means to connect the mock itself to the signal, so this exactly is what I did:
def test_cache():
with mock.patch('myapp.myfile.signal_handler_post_save_user', autospec=True) as mocked_handler:
post_save.connect(mocked_handler, sender=User, dispatch_uid='test_cache_mocked_handler')
# do stuff that will call the post_save of User
self.assertEquals(mocked_handler.call_count, 1) # standard django
# self.assert_equal(mocked_handler.call_count, 1) # when using django-nose
Notice that autospec=True in mock.patch is required in order to make post_save.connect to correctly work on a MagicMock, otherwise django will raise some exceptions and the connection will fail.
You can mock a django signal by mocking the ModelSignal class at django.db.models.signals.py like this:
#patch("django.db.models.signals.ModelSignal.send")
def test_overwhelming(self, mocker_signal):
obj = Object()
That should do the trick. Note that this will mock ALL signals no matter which object you are using.
If by any chance you use the mocker library instead, it can be done like this:
from mocker import Mocker, ARGS, KWARGS
def test_overwhelming(self):
mocker = Mocker()
# mock the post save signal
msave = mocker.replace("django.db.models.signals")
msave.post_save.send(KWARGS)
mocker.count(0, None)
with mocker:
obj = Object()
It's more lines but it works pretty well too :)
take a look at mock_django . It has support for signals
https://github.com/dcramer/mock-django/blob/master/tests/mock_django/signals/tests.py
In django 1.9 you can mock all receivers with something like this
# replace actual receivers with mocks
mocked_receivers = []
for i, receiver in enumerate(your_signal.receivers):
mock_receiver = Mock()
your_signal.receivers[i] = (receiver[0], mock_receiver)
mocked_receivers.append(mock_receiver)
... # whatever your test does
# ensure that mocked receivers have been called as expected
for mocked_receiver in mocked_receivers:
assert mocked_receiver.call_count == 1
mocked_receiver.assert_called_with(*your_args, sender="your_sender", signal=your_signal, **your_kwargs)
This replaces all receivers with mocks, eg ones you've registered, ones pluggable apps have registered and ones that django itself has registered. Don't be suprised if you use this on post_save and things start breaking.
You may want to inspect the receiver to determine if you actually want to mock it.
There is a way to mock django signals with a small class.
You should keep in mind that this would only mock the function as a django signal handler and not the original function; for example, if a m2mchange trigers a call to a function that calls your handler directly, mock.call_count would not be incremented. You would need a separate mock to keep track of those calls.
Here is the class in question:
class LocalDjangoSignalsMock():
def __init__(self, to_mock):
"""
Replaces registered django signals with MagicMocks
:param to_mock: list of signal handlers to mock
"""
self.mocks = {handler:MagicMock() for handler in to_mock}
self.reverse_mocks = {magicmock:mocked
for mocked,magicmock in self.mocks.items()}
django_signals = [signals.post_save, signals.m2m_changed]
self.registered_receivers = [signal.receivers
for signal in django_signals]
def _apply_mocks(self):
for receivers in self.registered_receivers:
for receiver_index in xrange(len(receivers)):
handler = receivers[receiver_index]
handler_function = handler[1]()
if handler_function in self.mocks:
receivers[receiver_index] = (
handler[0], self.mocks[handler_function])
def _reverse_mocks(self):
for receivers in self.registered_receivers:
for receiver_index in xrange(len(receivers)):
handler = receivers[receiver_index]
handler_function = handler[1]
if not isinstance(handler_function, MagicMock):
continue
receivers[receiver_index] = (
handler[0], weakref.ref(self.reverse_mocks[handler_function]))
def __enter__(self):
self._apply_mocks()
return self.mocks
def __exit__(self, *args):
self._reverse_mocks()
Example usage
to_mock = [my_handler]
with LocalDjangoSignalsMock(to_mock) as mocks:
my_trigger()
for mocked in to_mock:
assert(mocks[mocked].call_count)
# 'function {0} was called {1}'.format(
# mocked, mocked.call_count)
As you mentioned,
mock.patch('myapp.myfile._support_function') is correct but mock.patch('myapp.myfile.signal_handler_post_save_user') is wrong.
I think the reason is:
When init you test, some file import the signal's realization python file, then #receive decorator create a new signal connection.
In the test, mock.patch('myapp.myfile._support_function') will create another signal connection, so the original signal handler is called even if mocked.
Try to disconnect the signal connection before mock.patch('myapp.myfile._support_function'), like
post_save.disconnect(signal_handler_post_save_user)
with mock.patch("review.signals. signal_handler_post_save_user", autospec=True) as handler:
#do stuff

Categories