Skip to content

Commit

Permalink
Fixed #17604 - Added context-manager capability to assertTemplateUsed…
Browse files Browse the repository at this point in the history
… and assertTemplateNotUsed. Thanks Greg Müllegger.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17412 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
carljm committed Jan 31, 2012
1 parent 996f702 commit 82e99f4
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 5 deletions.
80 changes: 75 additions & 5 deletions django/test/testcases.py
Expand Up @@ -3,6 +3,7 @@
import os
import re
import sys
from copy import copy
from functools import wraps
from urlparse import urlsplit, urlunsplit
from xml.dom.minidom import parseString, Node
Expand All @@ -28,8 +29,10 @@
from django.http import QueryDict
from django.test import _doctest as doctest
from django.test.client import Client
from django.test.signals import template_rendered
from django.test.utils import (get_warnings_state, restore_warnings_state,
override_settings)
from django.test.utils import ContextList
from django.utils import simplejson, unittest as ut2
from django.utils.encoding import smart_str, force_unicode
from django.views.static import serve
Expand Down Expand Up @@ -260,8 +263,53 @@ def __exit__(self, exc_type, exc_value, traceback):
)


class SimpleTestCase(ut2.TestCase):
class _AssertTemplateUsedContext(object):
def __init__(self, test_case, template_name):
self.test_case = test_case
self.template_name = template_name
self.rendered_templates = []
self.rendered_template_names = []
self.context = ContextList()

def on_template_render(self, sender, signal, template, context, **kwargs):
self.rendered_templates.append(template)
self.rendered_template_names.append(template.name)
self.context.append(copy(context))

def test(self):
return self.template_name in self.rendered_template_names

def message(self):
return u'%s was not rendered.' % self.template_name

def __enter__(self):
template_rendered.connect(self.on_template_render)
return self

def __exit__(self, exc_type, exc_value, traceback):
template_rendered.disconnect(self.on_template_render)
if exc_type is not None:
return

if not self.test():
message = self.message()
if len(self.rendered_templates) == 0:
message += u' No template was rendered.'
else:
message += u' Following templates were rendered: %s' % (
', '.join(self.rendered_template_names))
self.test_case.fail(message)


class _AssertTemplateNotUsedContext(_AssertTemplateUsedContext):
def test(self):
return self.template_name not in self.rendered_template_names

def message(self):
return u'%s was rendered.' % self.template_name


class SimpleTestCase(ut2.TestCase):
def save_warnings_state(self):
"""
Saves the state of the warnings module
Expand Down Expand Up @@ -612,14 +660,25 @@ def assertFormError(self, response, form, field, errors, msg_prefix=''):
self.fail(msg_prefix + "The form '%s' was not used to render the"
" response" % form)

def assertTemplateUsed(self, response, template_name, msg_prefix=''):
def assertTemplateUsed(self, response=None, template_name=None, msg_prefix=''):
"""
Asserts that the template with the provided name was used in rendering
the response.
the response. Also useable as context manager.
"""
if response is None and template_name is None:
raise TypeError(u'response and/or template_name argument must be provided')

if msg_prefix:
msg_prefix += ": "

# use assertTemplateUsed as context manager
if not hasattr(response, 'templates') or (response is None and template_name):
if response:
template_name = response
response = None
context = _AssertTemplateUsedContext(self, template_name)
return context

template_names = [t.name for t in response.templates]
if not template_names:
self.fail(msg_prefix + "No templates used to render the response")
Expand All @@ -628,14 +687,25 @@ def assertTemplateUsed(self, response, template_name, msg_prefix=''):
" the response. Actual template(s) used: %s" %
(template_name, u', '.join(template_names)))

def assertTemplateNotUsed(self, response, template_name, msg_prefix=''):
def assertTemplateNotUsed(self, response=None, template_name=None, msg_prefix=''):
"""
Asserts that the template with the provided name was NOT used in
rendering the response.
rendering the response. Also useable as context manager.
"""
if response is None and template_name is None:
raise TypeError(u'response and/or template_name argument must be provided')

if msg_prefix:
msg_prefix += ": "

# use assertTemplateUsed as context manager
if not hasattr(response, 'templates') or (response is None and template_name):
if response:
template_name = response
response = None
context = _AssertTemplateNotUsedContext(self, template_name)
return context

template_names = [t.name for t in response.templates]
self.assertFalse(template_name in template_names,
msg_prefix + "Template '%s' was used unexpectedly in rendering"
Expand Down
15 changes: 15 additions & 0 deletions docs/releases/1.4.txt
Expand Up @@ -946,6 +946,21 @@ apply URL escaping again. This is wrong for URLs whose unquoted form contains
a ``%xx`` sequence, but such URLs are very unlikely to happen in the wild,
since they would confuse browsers too.

``assertTemplateUsed`` and ``assertTemplateNotUsed`` as context manager
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It is now possible to check whether a template was used or not in a block of
code with the :meth:`~django.test.testcase.TestCase.assertTemplateUsed` and
:meth:`~django.test.testcase.TestCase.assertTemplateNotUsed` assertions. They
can be used as a context manager::

with self.assertTemplateUsed('index.html'):
render_to_string('index.html')
with self.assertTemplateNotUsed('base.html'):
render_to_string('index.html')

See the :ref:`assertion documentation<assertions>` for more information.

Database connections after running the test suite
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
19 changes: 19 additions & 0 deletions docs/topics/testing.txt
Expand Up @@ -1575,11 +1575,30 @@ your test suite.

The name is a string such as ``'admin/index.html'``.

.. versionadded:: 1.4

You can also use this as a context manager. The code that is executed
under the with statement is then observed instead of a response::

# This is necessary in Python 2.5 to enable the with statement, in 2.6
# and up it is no longer necessary.
from __future__ import with_statement

with self.assertTemplateUsed('index.html'):
render_to_string('index.html')
with self.assertTemplateUsed(template_name='index.html'):
render_to_string('index.html')

.. method:: TestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')

Asserts that the template with the given name was *not* used in rendering
the response.

.. versionadded:: 1.4

You can use this as a context manager in the same way as
:func:`~TestCase.assertTemplateUsed`.

.. method:: TestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='')

Asserts that the response return a ``status_code`` redirect status, it
Expand Down
Empty file.
Empty file.
@@ -0,0 +1 @@
{% extends "template_used/base.html" %}
@@ -0,0 +1 @@
{% include "template_used/base.html" %}
87 changes: 87 additions & 0 deletions tests/regressiontests/test_utils/tests.py
@@ -1,6 +1,7 @@
from __future__ import with_statement, absolute_import

from django.forms import EmailField, IntegerField
from django.template.loader import render_to_string
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.utils.unittest import skip

Expand Down Expand Up @@ -88,6 +89,92 @@ def test_with_client(self):
self.client.get("/test_utils/get_person/%s/" % person.pk)


class AssertTemplateUsedContextManagerTests(TestCase):
def test_usage(self):
with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/base.html')

with self.assertTemplateUsed(template_name='template_used/base.html'):
render_to_string('template_used/base.html')

with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/include.html')

with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/extends.html')

with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/base.html')
render_to_string('template_used/base.html')

def test_nested_usage(self):
with self.assertTemplateUsed('template_used/base.html'):
with self.assertTemplateUsed('template_used/include.html'):
render_to_string('template_used/include.html')

with self.assertTemplateUsed('template_used/extends.html'):
with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/extends.html')

with self.assertTemplateUsed('template_used/base.html'):
with self.assertTemplateUsed('template_used/alternative.html'):
render_to_string('template_used/alternative.html')
render_to_string('template_used/base.html')

with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/extends.html')
with self.assertTemplateNotUsed('template_used/base.html'):
render_to_string('template_used/alternative.html')
render_to_string('template_used/base.html')

def test_not_used(self):
with self.assertTemplateNotUsed('template_used/base.html'):
pass
with self.assertTemplateNotUsed('template_used/alternative.html'):
pass

def test_error_message(self):
try:
with self.assertTemplateUsed('template_used/base.html'):
pass
except AssertionError, e:
self.assertTrue('template_used/base.html' in e.message)

try:
with self.assertTemplateUsed(template_name='template_used/base.html'):
pass
except AssertionError, e:
self.assertTrue('template_used/base.html' in e.message)

try:
with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/alternative.html')
except AssertionError, e:
self.assertTrue('template_used/base.html' in e.message, e.message)
self.assertTrue('template_used/alternative.html' in e.message, e.message)

def test_failure(self):
with self.assertRaises(TypeError):
with self.assertTemplateUsed():
pass

with self.assertRaises(AssertionError):
with self.assertTemplateUsed(''):
pass

with self.assertRaises(AssertionError):
with self.assertTemplateUsed(''):
render_to_string('template_used/base.html')

with self.assertRaises(AssertionError):
with self.assertTemplateUsed(template_name=''):
pass

with self.assertRaises(AssertionError):
with self.assertTemplateUsed('template_used/base.html'):
render_to_string('template_used/alternative.html')


class SaveRestoreWarningState(TestCase):
def test_save_restore_warnings_state(self):
"""
Expand Down

0 comments on commit 82e99f4

Please sign in to comment.