Why isn't Django unit test seeing a raised exception? - django

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

Related

How to mock a custom exception in python3?

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%

AssertionError on unit testing a celery task with autoretry, backoff and jitter

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.

RecursionError: when using factory boy

I can't use factory boy correctly.
That is my factories:
import factory
from harrispierce.models import Article, Journal, Section
class JournalFactory(factory.Factory):
class Meta:
model = Journal
name = factory.sequence(lambda n: 'Journal%d'%n)
#factory.post_generation
def sections(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of groups were passed in, use them
for section in extracted:
self.sections.add(section)
class SectionFactory(factory.Factory):
class Meta:
model = Section
name = factory.sequence(lambda n: 'Section%d'%n)
and my test:
import pytest
from django.test import TestCase, client
from harrispierce.factories import JournalFactory, SectionFactory
#pytest.mark.django_db
class TestIndex(TestCase):
#classmethod
def setUpTestData(cls):
cls.myclient = client.Client()
def test_index_view(self):
response = self.myclient.get('/')
assert response.status_code == 200
def test_index_content(self):
section0 = SectionFactory()
section1 = SectionFactory()
section2 = SectionFactory()
print('wijhdjk: ', section0)
journal1 = JournalFactory.create(sections=(section0, section1, section2))
response = self.myclient.get('/')
print('wijhdjk: ', journal1)
self.assertEquals(journal1.name, 'Section0')
self.assertContains(response, journal1.name)
But I get this when running pytest:
journal1 = JournalFactory.create(sections=(section0, section1, section2))
harrispierce_tests/test_index.py:22:
RecursionError: maximum recursion depth exceeded while calling a Python object
!!! Recursion detected (same locals & position)
One possible issue would be that you're not using the proper Factory base class: for a Django model, use factory.django.DjangoModelFactory.
This shouldn't cause the issue you have, though; a full stack trace would be useful.
Try to remove the #factory.post_generation section, and see whether you get a proper Journal object; then inspect what parameters where passed.
If this is not enough to fix your code, I suggest opening an issue on the factory_boy repository, with a reproducible test case (there are already some branches/commits attempting to reproduce a reported bug, which can be used as a template).

custom subroutine for WebDriverWait.until throws an error

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)

Django unit test views function - example

I'm newbie in Django tests. How to create Unit Test for this views function? My unit test function should import function from views? Please an example. This will help me to understand how it work
#maintainance_job
def time_to_end(request):
today = datetime.date.today()
datas = Data.objects.filter(start__lte=today,
other_date__gte=today)
for data in datas:
subject = _(u'Send email')
body = render_to_string('mail.txt',
{'data': data})
email = EmailMessage(subject, body,
'admin#admin.com',
[data.user.email])
email.send()
return HttpResponse('Done')
urls:
(r'^maintainance/jobs/time_to_end/$', 'content.views.time_to_end'),
There is a simpliest test for your case (place it in tests.py of a directory where is your view function):
from django.utils import unittest
from django.test.client import Client
class HttpTester( unittest.TestCase ):
def setUp( self ):
self._client = Client() # init a client for local access to pages of your site
def test_time_to_end( self ):
response = self._client.get( '/jobs/time_to_end/' )
# response = self._client.post( '/jobs/time_to_end/' ) - a 'POST' request
result = response.content
assert result != 'Done'
So, we use self._client to make 'get' and 'post' requests. Responses can be accessed by reading response.content (the full text of response) or by reading response.context if you use templates and want to access variables passing to the templates.
For example if your view normally must pass the dict with context variable 'result' to template:
{ 'result': "DONE" }
then you could check your result:
result = response.context[ 'result' ]
assert result != 'Done'
So, you wait your test will have the 'result' variable and it will be 'Done'. Otherwise you raise AssertionError (note assert statement).
If there is an exception then tests fails. AssertionError is an exception too.
More details - in the docs and in a book "Dive into Python".