Fixed #17604 - Added context-manager capability to assertTemplateUsed and assertTemplateNotUsed. Thanks Greg Müllegger.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17412 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Carl Meyer 2012-01-31 19:23:09 +00:00
parent f1dc83cb98
commit a678e9ea65
8 changed files with 198 additions and 5 deletions

View File

@ -3,6 +3,7 @@ from __future__ import with_statement
import os import os
import re import re
import sys import sys
from copy import copy
from functools import wraps from functools import wraps
from urlparse import urlsplit, urlunsplit from urlparse import urlsplit, urlunsplit
from xml.dom.minidom import parseString, Node from xml.dom.minidom import parseString, Node
@ -28,8 +29,10 @@ from django.forms.fields import CharField
from django.http import QueryDict from django.http import QueryDict
from django.test import _doctest as doctest from django.test import _doctest as doctest
from django.test.client import Client from django.test.client import Client
from django.test.signals import template_rendered
from django.test.utils import (get_warnings_state, restore_warnings_state, from django.test.utils import (get_warnings_state, restore_warnings_state,
override_settings) override_settings)
from django.test.utils import ContextList
from django.utils import simplejson, unittest as ut2 from django.utils import simplejson, unittest as ut2
from django.utils.encoding import smart_str, force_unicode from django.utils.encoding import smart_str, force_unicode
from django.views.static import serve from django.views.static import serve
@ -260,8 +263,53 @@ class _AssertNumQueriesContext(object):
) )
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): def save_warnings_state(self):
""" """
Saves the state of the warnings module Saves the state of the warnings module
@ -612,14 +660,25 @@ class TransactionTestCase(SimpleTestCase):
self.fail(msg_prefix + "The form '%s' was not used to render the" self.fail(msg_prefix + "The form '%s' was not used to render the"
" response" % form) " 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 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: if msg_prefix:
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] template_names = [t.name for t in response.templates]
if not template_names: if not template_names:
self.fail(msg_prefix + "No templates used to render the response") self.fail(msg_prefix + "No templates used to render the response")
@ -628,14 +687,25 @@ class TransactionTestCase(SimpleTestCase):
" the response. Actual template(s) used: %s" % " the response. Actual template(s) used: %s" %
(template_name, u', '.join(template_names))) (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 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: if msg_prefix:
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] template_names = [t.name for t in response.templates]
self.assertFalse(template_name in template_names, self.assertFalse(template_name in template_names,
msg_prefix + "Template '%s' was used unexpectedly in rendering" msg_prefix + "Template '%s' was used unexpectedly in rendering"

View File

@ -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, a ``%xx`` sequence, but such URLs are very unlikely to happen in the wild,
since they would confuse browsers too. 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 Database connections after running the test suite
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1575,11 +1575,30 @@ your test suite.
The name is a string such as ``'admin/index.html'``. 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='') .. method:: TestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')
Asserts that the template with the given name was *not* used in rendering Asserts that the template with the given name was *not* used in rendering
the response. 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='') .. 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 Asserts that the response return a ``status_code`` redirect status, it

View File

@ -0,0 +1 @@
{% extends "template_used/base.html" %}

View File

@ -0,0 +1 @@
{% include "template_used/base.html" %}

View File

@ -1,6 +1,7 @@
from __future__ import with_statement, absolute_import from __future__ import with_statement, absolute_import
from django.forms import EmailField, IntegerField from django.forms import EmailField, IntegerField
from django.template.loader import render_to_string
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.utils.unittest import skip from django.utils.unittest import skip
@ -88,6 +89,92 @@ class AssertNumQueriesContextManagerTests(TestCase):
self.client.get("/test_utils/get_person/%s/" % person.pk) 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): class SaveRestoreWarningState(TestCase):
def test_save_restore_warnings_state(self): def test_save_restore_warnings_state(self):
""" """