Strange behaviour mock’s side_effect function - python

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 '''

Related

pytest capsys with custom exception

I am having trouble testing the output of a custom exception in pytest.
import pytest
class CustomException(Exception):
def __init__(self, extra_message: str):
self.message = extra_message
super().__init__(self.message)
# Should not need to print this as Exception already does this
# print(self.message)
def test_should_get_capsys_output(capsys):
with pytest.raises(CustomException):
raise CustomException("This should be here.")
out, err = capsys.readouterr()
# This should not be true
assert out == ''
assert err == ''
assert 'This' not in out
This example should not pass as I should be able to assert something came out in the output. If I print(self.message) I end up getting the message printed twice when actually used but only then does capsys collect stdout.
I've also tried with variations of caplog and capfd to no avail. [This SO solution] recommends adding an output to the with pytest.raises(...) as info and testing the info but I would have expected capsys to work as well.
Thank you for your time.
I'm sort of confused by what you're asking?
A python exception will stop execution of the current test, even within pytest. So by raising the exception inside a context manager (with statement) we're simulating/catching it before it gets a chance to stop our current test and go to stderr.

How to test Exception in signal?

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)

Mocks not getting hit in Python unit tests

I'm new to Python, but I've done quite a bit of unit testing in C# and JavaScript. I'm having trouble figuring out the mocking framework in Python. Here's what I have (trimmed down):
invoice_business.py
import ims.repository.invoice_repository as invoiceRepository
import logging
logger = logging.getLogger(__name__)
def update_invoice_statuses(invoices):
for invoice in invoices:
dbInvoice = invoiceRepository.get(invoice.invoice_id)
print("dbInvoice is %s" % dbInvoice) #prints <MagicMock etc.>
if dbInvoice is None:
logger.error("Unable to update status for invoice %d" % invoice.invoice_id)
continue;
test_invoice_business.py
from unittest import TestCase, mock
import logging
import ims.business.invoice_business as business
class UpdateInvoiceTests(TestCase):
#mock.patch("ims.business.invoice_business.invoiceRepository")
#mock.patch("ims.business.invoice_business.logger")
def test_invoiceDoesNotExist_logsErrorAndContinues(self, invoiceRepoMock, loggerMock):
#Arrange
invoice = Invoice(123)
invoice.set_status(InvoiceStatus.Filed, None)
invoiceRepoMock.get.return_value(33)
#Act
business.update_invoice_statuses([invoice])
#Assert
invoiceRepoMock.get.assert_called_once_with(123)
loggerMock.error.assert_called_once_with("Unable to update status for invoice 123")
The test fails with
AssertionError: Expected 'get' to be called once. Called 0 times.
The print statement in update_invoice_statuses gets hit, though, because I see the output of
dbInvoice is <MagicMock name='invoiceRepository.get()' id='xxxx'>
Any idea what I'm doing wrong here?
Edit: After #chepner's help, I ran into another assertion error and realized it was because I should be using invoiceRepoMock.get.return_value = None rather than .return_value(None)
The mock arguments to your test function are swapped. The inner decorator (for the logger) is applied first, so the mock logger should be the first argument to your method.
#mock.patch("ims.business.invoice_business.invoiceRepository")
#mock.patch("ims.business.invoice_business.logger")
def test_invoiceDoesNotExist_logsErrorAndContinues(self, loggerMock, invoiceRepoMock):
...

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 do I catch an error raised in a decorator?

I am using flask-auth, which provides some helper decorators. I've added all the various methods below, but the question I want to ask is how to catch any issues thrown by the authorized_handler decorator. It's a general question about decorators, but I thought a real example might help.
If the decorator blows up, how could I catch it?
import os
import flask
import flask_oauth
CONSUMER_KEY = os.environ['CONSUMER_KEY']
CONSUMER_SECRET = os.environ['CONSUMER_SECRET']
oauth = flask_oauth.OAuth()
twitter = oauth.remote_app(
'twitter',
base_url='https://api.twitter.com/1/',
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authenticate',
consumer_key=CONSUMER_KEY,
consumer_secret=CONSUMER_SECRET
)
app = flask.Flask(__name__)
#app.route('/login')
def login():
return twitter.authorize(
callback=url_for(
'oauth_authorized',
next=request.args.get('next') or request.referrer or None)
)
#app.route('/oauth-authorized')
# what happens if this raises an error?
#twitter.authorized_handler
def oauth_authorized(resp):
print 'foo-bar'
Function definitions are executed. Therefore, assuming the exception raised is specific to that decorator, you can wrap the function definition, including decorators, in a try/except:
try:
#app.route('/oauth-authorized')
#twitter.authorized_handler
def oauth_authorized(resp):
print 'foo-bar'
except WhateverError as e:
print "twitter.authorized_handler raised an error", e
Of course, this will leave oauth_authorized undefined if the exception is raised. This is probably OK in your case since you probably don't want it to be be routed anyway. But if this isn't what you want, you could add a dummy definition to your except block.
Or, since decorators are just functions (well, any callable) and the # notation is merely syntactic sugar for a function call, you can wrap just the authorized_handler decoration in try/except:
def oauth_authorized(resp):
print 'foo-bar'
try: # apply decorator
oauth_authorized = twitter.authorized_handler(oauth_authorized)
except Exception as e:
print "twitter.authorized_handler raised an error", e
else: # no error decorating with authorized_handler, apply app.route
oauth_authorized = app.route('/oauth-authorized')(oauth_authorized)
This will leave you with the undecorated version of the function if the authorized_handler decoration fails, but it will not be routed. You could even put the above in its own function and use it as a decorator!

Categories