Skip to content

Commit

Permalink
Adds tests for invalid template variables
Browse files Browse the repository at this point in the history
Django catches all `VariableDoesNotExist` exceptions to replace
them in templates with a modifiable string that you can define in
your settings.
Sadly that doesn't allow you to find them in unit tests.

`_fail_for_invalid_template_variable` sets the setting
`TEMPLATE_STRING_IF_INVALID` to a custom class that not only fails
the current test but prints a pretty message including the template
name.

A new marker allows disabling this behavior, eg:

    @pytest.mark.ignore_template_errors
    def test_something():
        pass

This marker sets the setting to None, if you want it to be a string,
you can use the `settings` fixture to set it to your desired value.
  • Loading branch information
codingjoe committed Mar 22, 2015
1 parent 1e72766 commit e7b1e95
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
69 changes: 69 additions & 0 deletions pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,75 @@ def restore():
request.addfinalizer(restore)


@pytest.fixture(autouse=True, scope='session')
def _fail_for_invalid_template_variable(request):
"""Fixture that fails for invalid variables in templates.
This fixture will fail each test that uses django template rendering
should at template contain an invalid template variable.
It does not raise an exception, but fails, as the stack trace doesn't
offer any helpful information to debug.
This behavior can be switched of using the marker:
``ignore_template_errors``
"""
class InvalidVarException(object):

"""Custom handler for invalid strings in templates."""

def __contains__(self, key):
"""There is a test for '%s' in TEMPLATE_STRING_IF_INVALID."""
return key == '%s'

def _get_template(self):
import inspect
from functools import reduce

from django.template import Template

stack = inspect.stack()
# finding the ``render`` needle in the stack
frame = reduce(
lambda x, y: y[3] == 'render' and 'base.py' in y[1] and y or x,
stack
)
# assert 0, stack
frame = frame[0]
# finding only the frame locals in all frame members
f_locals = reduce(
lambda x, y: y[0] == 'f_locals' and y or x,
inspect.getmembers(frame)
)[1]
# ``django.template.base.Template``
template = f_locals['self']
if isinstance(template, Template):
return template

def __mod__(self, var):
"""Handle TEMPLATE_STRING_IF_INVALID % var."""
template = self._get_template()
if template:
msg = "Undefined template variable '%s' in '%s'" % (var, template.name)
else:
msg = "Undefined template variable '%s'" % var
pytest.fail(msg, pytrace=False)

if django_settings_is_configured():
from django.conf import settings

settings.TEMPLATE_STRING_IF_INVALID = InvalidVarException()


@pytest.fixture(autouse=True)
def _template_string_if_invalid_marker(request):
"""Apply the @pytest.mark.ignore_template_errors marker,
internal to pytest-django."""
marker = request.keywords.get('ignore_template_errors', None)
if marker and django_settings_is_configured():
from django.conf import settings

settings.TEMPLATE_STRING_IF_INVALID = None

# ############### Helper Functions ################


Expand Down
53 changes: 53 additions & 0 deletions tests/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,59 @@ def test_mail_again():
test_mail()


@pytest.mark.django_project(extra_settings="""
INSTALLED_APPS = [
'tpkg.app',
]
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
TEMPLATE_DIRS = (
)
ROOT_URLCONF = 'tpkg.app.urls'
""")
def test_invalid_template_variable(django_testdir):
django_testdir.create_app_file("""
try:
from django.conf.urls import patterns # Django >1.4
except ImportError:
from django.conf.urls.defaults import patterns # Django 1.3
urlpatterns = patterns(
'',
(r'invalid_template/', 'tpkg.app.views.invalid_template'),
)
""", 'urls.py')
django_testdir.create_app_file("""
from django.shortcuts import render
def invalid_template(request):
return render(request, 'invalid_template.html', {})
""", 'views.py')
django_testdir.create_app_file(
"<div>{{ invalid_var }}</div>",
'templates/invalid_template.html'
)
django_testdir.create_test_module('''
import pytest
def test_for_invalid_template(client):
client.get('/invalid_template/')
@pytest.mark.ignore_template_errors
def test_ignore(client):
client.get('/invalid_template/')
''')
result = django_testdir.runpytest('-s')
result.stdout.fnmatch_lines_random([
"tpkg/test_the_test.py F.",
"Undefined template variable 'invalid_var' in 'invalid_template.html'",
])



@pytest.mark.django_db
def test_database_rollback():
assert Item.objects.count() == 0
Expand Down

0 comments on commit e7b1e95

Please sign in to comment.