I'm doing some unit test works in python using unittest module. When i try to unittest for the custom exception, it seems like it's not working. Below is my code
# src.py
from exceptions import ClusterException, IndexingException
from utils import create_index, index_to_es
def method(bucket, key, file):
try:
s3_obj = get_object(bucket, key)
....
....
create_index(index_name, index_mapping)
index_to_es(df)
except ClusterException as e:
raise ClusterException(e)
except Exception:
raise IndexingException(e)
Here i need to test for the ClusterException exception block. So i'm mocking create_index() method to raise a ClusterException error. My testing code is
# test_src.py
with mock.patch('src.ClusterException') as mocked_cluster_exception:
mocked_cluster_exception.side_effect = ClusterException("Bad Cluster Error")
with mock.patch('src.create_index') as mocked_create_index:
mocked_create_index.side_effect = ClusterException("Index creation error")
self.assertRaises(ClusterException, method, 'bucket', 'key', 'file')
And my exception file is
# exceptions.py
class ClusterException(Exception):
pass
class IndexingException(Exception):
pass
But when i run this the testing is getting failed with below message. What am i missing here?
TypeError: catching classes that do not inherit from BaseException is not allowed
You don't need to patch the src.ClusterException. You should patch the create_index() function to raise a ClusterException.
E.g.
src.py:
from exceptions import ClusterException, IndexingException
from utils import create_index
def method(bucket, key, file):
try:
index_name = 'index_name'
index_mapping = 'index_mapping'
create_index(index_name, index_mapping)
except ClusterException as e:
print(e)
raise ClusterException(e)
except Exception as e:
raise IndexingException(e)
utils.py:
def create_index(name, map):
pass
exceptions.py:
class ClusterException(Exception):
pass
class IndexingException(Exception):
pass
test_src.py:
from unittest import mock, main, TestCase
from exceptions import ClusterException
from src import method
class TestSrc(TestCase):
def test_method_to_raise_cluster_exception(self):
with mock.patch('src.create_index') as mocked_create_index:
mocked_create_index.side_effect = ClusterException("Index creation error")
self.assertRaises(ClusterException, method, 'bucket', 'key', 'file')
mocked_create_index.assert_called_once_with('index_name', 'index_mapping')
if __name__ == '__main__':
main()
unit test result:
Index creation error
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
------------------------------------------------------------------------
src/stackoverflow/68203609/exceptions.py 4 0 100%
src/stackoverflow/68203609/src.py 12 2 83% 13-14
src/stackoverflow/68203609/test_src.py 11 0 100%
src/stackoverflow/68203609/utils.py 2 1 50% 2
------------------------------------------------------------------------
TOTAL 29 3 90%
Related
Using celery 4.3.0. I tried to write a unit test for the following task.
from django.core.exceptions import ObjectDoesNotExist
#shared_task(autoretry_for=(ObjectDoesNotExist,), max_retries=5, retry_backoff=10)
def process_something(data):
product = Product()
product.process(data)
Unit test:
#mock.patch('proj.tasks.Product')
#mock.patch('proj.tasks.process_something.retry')
def test_process_something_retry_failed_task(self, process_something_retry, mock_product):
mock_object = mock.MagicMock()
mock_product.return_value = mock_object
mock_object.process.side_effect = error = ObjectDoesNotExist()
with pytest.raises(ObjectDoesNotExist):
process_something(self.data)
process_something_retry.assert_called_with(exc=error)
This is the error I get after running the test:
#wraps(task.run)
def run(*args, **kwargs):
try:
return task._orig_run(*args, **kwargs)
except autoretry_for as exc:
if retry_backoff:
retry_kwargs['countdown'] = \
get_exponential_backoff_interval(
factor=retry_backoff,
retries=task.request.retries,
maximum=retry_backoff_max,
full_jitter=retry_jitter)
> raise task.retry(exc=exc, **retry_kwargs)
E TypeError: exceptions must derive from BaseException
I understand it is because of the exception. I replaced ObjectDoesNotExist everywhere with Exception instead. After running the test, I get this error:
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\nActual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher((args, kwargs))
actual = self._call_matcher(self.call_args)
if expected != actual:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: retry(exc=Exception())
E Actual: retry(exc=Exception(), countdown=7)
Please let me know how I can fix both the errors.
I had the similar issue, while I was working on tests to ensure that the celery retry logic was covering my specific scenarios. What worked for me was to use explicit retry instead of the autoretry_for parameter.
I have adjusted your code to my solution. Although my solution didn't use
shared_task I think It should work likewise. Tested on celery==5.1.2
task:
from django.core.exceptions import ObjectDoesNotExist
#shared_task(bind=True, max_retries=5, retry_backoff=10)
def process_something(self, data):
try:
product = Product()
product.process(data)
except ObjectDoesNotExist as exc:
raise self.retry(exc=exc)
test:
from proj.tasks import Product # I assume the Product class is located here
from django.core.exceptions import ObjectDoesNotExist
import celery
#mock.patch.object(Product, "__init__", Mock(return_value=None)) # just mocking the init method
#mock.patch.object(Product, "process")
#mock.patch('proj.tasks.process_something.retry')
def test_process_something_retry_failed_task(self, retry_mock, process_mock):
exc = ObjectDoesNotExist()
process_mock.side_effect = exc
retry_mock.side_effect = celery.exceptions.Retry
with pytest.raises(celery.exceptions.Retry):
process_something(self.data)
retry_mock.assert_called_with(exc=exc)
In my problem I also was using custom exceptions. With this solution I didnt need change the type of my exceptions.
I am new to programming and Django in general. I am trying to test one of my functions to make sure that a validation error is raised. The test confirms that the error is raised but also says the test Failed. How is this possible?
**models.py**
def check_user_words(sender, instance, **kwargs):
for field in instance._meta.get_fields():
#field_name = getattr(instance, field.attname)
if (isinstance(field, models.CharField) and
contains_bad_words(getattr(instance, field.attname))):
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
#tests.py
from __future__ import unicode_literals
import datetime
from django.test import TestCase
from django.utils import timezone
from django.test import TestCase
from django.urls import reverse
from .models import Question, Choice, contains_bad_words, check_user_words
from django.core.exceptions import ValidationError
def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class ContainsBadWordsTests(TestCase):
def test_check_user_words(self):
question = create_question(question_text="What a minute bucko", days=1)
with self.assertRaises(ValidationError):
check_user_words(question)
question.full_clean()
#after running python manage.py test polls
......
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
ValidationError: [u"We don't use words like 'What a minute bucko' around here!"]
models.py How I import
from __future__ import unicode_literals .... (and others)
filepath = "polls/static/polls/blacklist.yaml"
config = yaml_loader(filepath)
blacklist = [word.lower() for word in config['blacklist']]
def contains_bad_words(user_input_txt):
""" remove punctuation from text
and make it case-insensitive"""
user_typ = user_input_txt.encode()
translate_table = maketrans(string.punctuation, 32 * " ")
words = user_typ.translate(translate_table).lower().split()
for bad_word in blacklist:
for word in words:
if word == bad_word:
return True
return False
#receiver(pre_save)
def check_user_words(sender, instance, **kwargs):
for field in instance._meta.get_fields():
if (isinstance(field, models.CharField) and
contains_bad_words(getattr(instance, field.attname))):
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
We need to see more of your code (specifically create_question() and how check_user_words is connected to a signal) to be sure, but I think the issue is that you are using a post_save signal handler to execute check_user_words().
If this is the case, then the reason your test is failing is that create_question() will cause the post_save signal to fire, and check_user_words() will be executed immediately - i.e., before the with self.assertRaises context, and hence your test fails.
If this is the case, then try this:
def test_check_user_words(self):
with self.assertRaises(ValidationError):
create_question(question_text="What a minute bucko", days=1)
This test should now pass, because the validation error will be thrown as soon as you try to create the question.
Note however that doing this in a signal will result in an uncaught exception when something tries to save an object. Depending on what your use case is, you might be better off doing this in the clean() method of the model itself (see docs here), because this will cause appropriate errors to be reported on model forms etc:
def clean(self):
for field in instance._meta.get_fields():
if (isinstance(field, models.CharField) and contains_bad_words(getattr(instance, field.attname))):
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
(and then drop your signal handler). Then you can test this with:
q = create_question(question_text="What a minute bucko", days=1)
with self.assertRaises(ValidationError):
q.clean()
I'm trying to test a static method in one of my models but the test is not seeing an exception that's being raised and I don't understand why.
Here's the model and static method:
# models.py
class List(models.Model):
owner = models.ForeignKey(User)
type = models.ForeignKey('ListType', help_text=_('Type of list'))
name = models.CharField(_('list'), max_length=128, help_text=_('Name of list'))
class ListType(models.Model):
type = models.CharField(_('type'), max_length=16)
#staticmethod
def read_list(list_id, list_name, owner, list_type):
try:
return List.objects.get(pk=list_id, name=list_name, owner=owner, type=list_type)
except List.DoesNotExist:
return None
Here's the test:
# tests.py
from django.test import TestCase
from .factories import *
from .models import List, ListType
class TestFuncs(TestCase):
def test_read_list_exc(self):
with self.assertRaises(List.DoesNotExist):
uf = UserFactory()
lt = ListType.objects.get(type='Member')
lf = ListFactory(owner=uf, type=lt, name='foo')
# I've created one list but its name isn't 'bar'
list = List.read_list(999, 'bar', uf, lt)
If I set a debugging breakpoint in the read_list method and run the test, I do see the exception being raised:
# set_trace output:
(<class 'list.models.DoesNotExist'>, DoesNotExist('List matching query does not exist.',))
# test output:
...
File "...."
list = List.read_list(999, 'bar', uf, lt)
AssertionError: DoesNotExist not raised
I've read other questions here about how to detect this type of exception and I thought I was doing it right. Just for fun, I changed the test to the following but that didn't resolve the problem:
...
with self.assertRaises(list.models.DoesNotExist):
...
Can anyone see what I'm doing wrong?
In the static method you catch the exception and return None.
You could either change the test to use assertIsNone.
l = List.read_list(999, 'bar', uf, lt) # don't use list as a variable
self.assertIsNone(l)
Or if you really do want the method to raise the exception, then remove the try..except.
#staticmethod
def read_list(list_id, list_name, owner, list_type):
return List.objects.get(pk=list_id, name=list_name, owner=owner, type=list_type)
You already handled DoesNotExist exception inside read_list so it's not thrown to the test case.
To throw exception you can use raise operator:
#staticmethod
def read_list(list_id, list_name, owner, list_type):
try:
return List.objects.get(pk=list_id, name=list_name, owner=owner, type=list_type)
except List.DoesNotExist as e:
some actions to handle exception, for example logging
...
raise e
I am testing an angular web-app using selenium and python. For my test, I am setting up some data using API calls. Now I want to wait until the data shows up in the front-end before proceeding with my test. We currently have a 60 second wait to overcome this problem; however, I was hoping for a smarter wait and wrote the following code:
def wait_for_plan_to_appear(self,driver,plan_locator):
plan_name_element = UNDEF
try:
self.navigateToPlanPage()
plan_name_element = driver.find_element_by_xpath(plan_locator)
except NoSuchElementException:
pass
return plan_name_element
def find_plan_name_element(self,plan_id):
plan_locator = '//*[#data-hraf-id="'+plan_id+'-plan-name"]'
plan_name_element = UNDEF
try:
plan_name_element = WebDriverWait(self.driver,60,2).until(self.wait_for_plan_to_appear(self.driver,plan_locator))
except TimeoutException:
self.logger.debug("Could not find the plan with plan_id = "+plan_id)
return plan_name_element
In my test script, I am calling:
self.find_plan_name_element('e7fa25a5-0b39-4a97-b99f-44c48439ce99') # the long string is the plan-id
However, when I run this code - i get following error:
error: 'int' object is not callable"
If I change the wait_for_plan_to_appear such that it returns a boolean, it throws error:
error: 'bool' object is not callable"
Has someone seen/resolved this in their work ? Thanks
I would use "...".format() to automatically convert the plan_id to a string.
Moreover you could simplify the waiter by using an expected condition :
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException
class element_loaded_and_displayed(object):
""" An expectation for checking that an element is present on the DOM of a
page and visible. Refreshes the page if the element is not present.
returns the WebElement once it is located and visible.
"""
def __init__(self, locator):
self.locator = locator
def __call__(self, driver):
try:
element = driver.find_element(*self.locator)
return element if element.is_displayed() else False
except StaleElementReferenceException:
return False
except NoSuchElementException as ex:
driver.refresh()
raise ex
def find_plan_name_element(self, plan_id):
plan_locator = (By.CSS_SELECTOR, "[data-hraf-id='{0}-plan-name']".format(plan_id))
err_message = "Could not find the plan with plan_id = {0}".format(plan_id)
wait = WebDriverWait(self.driver, timeout=60, poll_frequency=2)
return wait.until(element_loaded_and_displayed(plan_locator), err_message)
I have Celery 3.1.18 running with Django 1.6.11 and RabbitMQ 3.5.4, and trying to test my async task in a failure state (CELERY_ALWAYS_EAGER=True). However, I cannot get the proper "result" in the error callback. The example in the Celery docs shows:
#app.task(bind=True)
def error_handler(self, uuid):
result = self.app.AsyncResult(uuid)
print('Task {0} raised exception: {1!r}\n{2!r}'.format(
uuid, result.result, result.traceback))
When I do this, my result is still "PENDING", result.result = '', and result.traceback=''. But the actual result returned by my .apply_async call has the right "FAILURE" state and traceback.
My code (basically a Django Rest Framework RESTful endpoint that parses a .tar.gz file, and then sends a notification back to the user, when the file is done parsing):
views.py:
from producer_main.celery import app as celery_app
#celery_app.task()
def _upload_error_simple(uuid):
print uuid
result = celery_app.AsyncResult(uuid)
print result.backend
print result.state
print result.result
print result.traceback
msg = 'Task {0} raised exception: {1!r}\n{2!r}'.format(uuid,
result.result,
result.traceback)
class UploadNewFile(APIView):
def post(self, request, repository_id, format=None):
try:
uploaded_file = self.data['files'][self.data['files'].keys()[0]]
self.path = default_storage.save('{0}/{1}'.format(settings.MEDIA_ROOT,
uploaded_file.name),
uploaded_file)
print type(import_file)
self.async_result = import_file.apply_async((self.path, request.user),
link_error=_upload_error_simple.s())
print 'results from self.async_result:'
print self.async_result.id
print self.async_result.backend
print self.async_result.state
print self.async_result.result
print self.async_result.traceback
return Response()
except (PermissionDenied, InvalidArgument, NotFound, KeyError) as ex:
gutils.handle_exceptions(ex)
tasks.py:
from producer_main.celery import app
from utilities.general import upload_class
#app.task
def import_file(path, user):
"""Asynchronously import a course."""
upload_class(path, user)
celery.py:
"""
As described in
http://celery.readthedocs.org/en/latest/django/first-steps-with-django.html
"""
from __future__ import absolute_import
import os
import logging
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'producer_main.settings')
from django.conf import settings
log = logging.getLogger(__name__)
app = Celery('producer') # pylint: disable=invalid-name
# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) # pragma: no cover
#app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
My backend is configured as such:
CELERY_ALWAYS_EAGER = True
CELERY_EAGER_PROPAGATES_EXCEPTIONS = False
BROKER_URL = 'amqp://'
CELERY_RESULT_BACKEND = 'redis://localhost'
CELERY_RESULT_PERSISTENT = True
CELERY_IGNORE_RESULT = False
When I run my unittest for the link_error state, I get:
Creating test database for alias 'default'...
<class 'celery.local.PromiseProxy'>
130ccf13-c2a0-4bde-8d49-e17eeb1b0115
<celery.backends.redis.RedisBackend object at 0x10aa2e110>
PENDING
None
None
results from self.async_result:
130ccf13-c2a0-4bde-8d49-e17eeb1b0115
None
FAILURE
Non .zip / .tar.gz file passed in.
Traceback (most recent call last):
So the task results are not available in my _upload_error_simple() method, but they are available from the self.async_result returned variable...
I could not get the link and link_error callbacks to work, so I finally had to use the on_failure and on_success task methods described in the docs and this SO question. My tasks.py then looks like:
class ErrorHandlingTask(Task):
abstract = True
def on_failure(self, exc, task_id, targs, tkwargs, einfo):
msg = 'Import of {0} raised exception: {1!r}'.format(targs[0].split('/')[-1],
str(exc))
def on_success(self, retval, task_id, targs, tkwargs):
msg = "Upload successful. You may now view your course."
#app.task(base=ErrorHandlingTask)
def import_file(path, user):
"""Asynchronously import a course."""
upload_class(path, user)
You appear to have _upload_error() as a bound method of your class - this is probably not what you want. try making it a stand-along task:
#celery_app.task(bind=True)
def _upload_error(self, uuid):
result = celery_app.AsyncResult(uuid)
msg = 'Task {0} raised exception: {1!r}\n{2!r}'.format(uuid,
result.result,
result.traceback)
class Whatever(object):
....
self.async_result = import_file.apply_async((self.path, request.user),
link=self._upload_success.s(
"Upload finished."),
link_error=_upload_error.s())
in fact there's no need for the self paramater since it's not used so you could just do this:
#celery_app.task()
def _upload_error(uuid):
result = celery_app.AsyncResult(uuid)
msg = 'Task {0} raised exception: {1!r}\n{2!r}'.format(uuid,
result.result,
result.traceback)
note the absence of bind=True and self
Be careful with UUID instance!
If you will try to get status of a task with id not string type but UUID type, you will only get PENDING status.
from uuid import UUID
from celery.result import AsyncResult
task_id = UUID('d4337c01-4402-48e9-9e9c-6e9919d5e282')
print(AsyncResult(task_id).state)
# PENDING
print(AsyncResult(str(task_id)).state)
# SUCCESS