Testing

Testing Templates

Template testing ensures your Django templates render correctly, display the right content, and handle various data scenarios properly. While Django templates are primarily presentation logic, testing them is crucial for ensuring your application's user interface works as expected.

Testing Templates

Template testing ensures your Django templates render correctly, display the right content, and handle various data scenarios properly. While Django templates are primarily presentation logic, testing them is crucial for ensuring your application's user interface works as expected.

Basic Template Testing

Testing Template Rendering

from django.test import TestCase, RequestFactory
from django.template import Context, Template
from django.template.loader import render_to_string
from django.contrib.auth.models import User
from blog.models import BlogPost, Category

class TemplateRenderingTests(TestCase):
    """Test basic template rendering"""
    
    def setUp(self):
        """Set up test data"""
        self.factory = RequestFactory()
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        self.category = Category.objects.create(
            name='Technology',
            slug='technology'
        )
        
        self.post = BlogPost.objects.create(
            title='Test Blog Post',
            slug='test-blog-post',
            content='This is test content for the blog post.',
            author=self.user,
            category=self.category,
            status='published'
        )
    
    def test_template_renders_without_error(self):
        """Test template renders without syntax errors"""
        
        template_string = """
        <h1>{{ post.title }}</h1>
        <p>By {{ post.author.username }}</p>
        <div>{{ post.content }}</div>
        """
        
        template = Template(template_string)
        context = Context({'post': self.post})
        
        # Should not raise TemplateSyntaxError
        try:
            rendered = template.render(context)
            self.assertIn(self.post.title, rendered)
            self.assertIn(self.user.username, rendered)
            self.assertIn(self.post.content, rendered)
        except Exception as e:
            self.fail(f"Template rendering failed: {e}")
    
    def test_template_with_missing_context_variable(self):
        """Test template behavior with missing context variables"""
        
        template_string = """
        <h1>{{ post.title }}</h1>
        <p>{{ missing_variable }}</p>
        """
        
        template = Template(template_string)
        context = Context({'post': self.post})
        
        rendered = template.render(context)
        
        # Should render post title but handle missing variable gracefully
        self.assertIn(self.post.title, rendered)
        # Missing variables render as empty string by default
        self.assertNotIn('missing_variable', rendered)
    
    def test_template_file_rendering(self):
        """Test rendering actual template files"""
        
        # Test using render_to_string
        context = {
            'post': self.post,
            'user': self.user
        }
        
        try:
            rendered = render_to_string('blog/post_detail.html', context)
            
            # Check expected content is present
            self.assertIn(self.post.title, rendered)
            self.assertIn(self.post.content, rendered)
            
        except Exception as e:
            # Template file might not exist in test environment
            self.skipTest(f"Template file not found: {e}")
    
    def test_template_with_conditional_logic(self):
        """Test template conditional rendering"""
        
        template_string = """
        {% if post.is_published %}
            <span class="published">Published</span>
        {% else %}
            <span class="draft">Draft</span>
        {% endif %}
        
        {% if post.author == user %}
            <a href="/edit/">Edit Post</a>
        {% endif %}
        """
        
        template = Template(template_string)
        
        # Test as post author
        context = Context({
            'post': self.post,
            'user': self.user
        })
        
        rendered = template.render(context)
        
        # Should show published status and edit link
        self.assertIn('Published', rendered)
        self.assertIn('Edit Post', rendered)
        
        # Test as different user
        other_user = User.objects.create_user('otheruser', 'other@example.com', 'pass')
        context = Context({
            'post': self.post,
            'user': other_user
        })
        
        rendered = template.render(context)
        
        # Should show published status but no edit link
        self.assertIn('Published', rendered)
        self.assertNotIn('Edit Post', rendered)

Testing Template Context

class TemplateContextTests(TestCase):
    """Test template context variables"""
    
    def setUp(self):
        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.category = Category.objects.create(name='Tech', slug='tech')
        
        # Create multiple posts for testing
        for i in range(5):
            BlogPost.objects.create(
                title=f'Post {i}',
                content=f'Content for post {i}',
                author=self.user,
                category=self.category,
                status='published'
            )
    
    def test_view_provides_correct_context(self):
        """Test view provides correct context to template"""
        
        response = self.client.get('/blog/')
        
        # Check context variables
        self.assertIn('posts', response.context)
        self.assertIn('categories', response.context)
        
        # Check context data
        posts = response.context['posts']
        self.assertEqual(len(posts), 5)
        
        # Check all posts are published
        for post in posts:
            self.assertEqual(post.status, 'published')
    
    def test_template_receives_user_context(self):
        """Test template receives user context"""
        
        self.client.login(username='testuser', password='testpass123')
        
        response = self.client.get('/blog/')
        
        # Check user context
        self.assertIn('user', response.context)
        self.assertEqual(response.context['user'], self.user)
        self.assertTrue(response.context['user'].is_authenticated)
    
    def test_template_context_processors(self):
        """Test custom context processors"""
        
        response = self.client.get('/blog/')
        
        # Check context processors add expected variables
        # (These would be defined in your context processors)
        self.assertIn('site_name', response.context)
        self.assertIn('current_year', response.context)
        
        # Check values
        self.assertEqual(response.context['site_name'], 'My Blog')
        self.assertIsInstance(response.context['current_year'], int)

Testing Template Tags

Testing Built-in Template Tags

class BuiltinTemplateTagTests(TestCase):
    """Test built-in Django template tags"""
    
    def setUp(self):
        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.category = Category.objects.create(name='Tech', slug='tech')
        
        self.posts = []
        for i in range(3):
            post = BlogPost.objects.create(
                title=f'Post {i}',
                content=f'Content for post {i}',
                author=self.user,
                category=self.category,
                status='published'
            )
            self.posts.append(post)
    
    def test_for_loop_template_tag(self):
        """Test for loop template tag"""
        
        template_string = """
        {% for post in posts %}
            <article>{{ post.title }}</article>
        {% empty %}
            <p>No posts available</p>
        {% endfor %}
        """
        
        template = Template(template_string)
        
        # Test with posts
        context = Context({'posts': self.posts})
        rendered = template.render(context)
        
        for post in self.posts:
            self.assertIn(post.title, rendered)
        self.assertNotIn('No posts available', rendered)
        
        # Test with empty list
        context = Context({'posts': []})
        rendered = template.render(context)
        
        self.assertIn('No posts available', rendered)
        for post in self.posts:
            self.assertNotIn(post.title, rendered)
    
    def test_if_template_tag(self):
        """Test if template tag with various conditions"""
        
        template_string = """
        {% if posts %}
            <p>{{ posts|length }} posts found</p>
        {% endif %}
        
        {% if posts|length > 2 %}
            <p>Many posts</p>
        {% elif posts|length > 0 %}
            <p>Few posts</p>
        {% else %}
            <p>No posts</p>
        {% endif %}
        """
        
        template = Template(template_string)
        
        # Test with 3 posts
        context = Context({'posts': self.posts})
        rendered = template.render(context)
        
        self.assertIn('3 posts found', rendered)
        self.assertIn('Many posts', rendered)
        
        # Test with 1 post
        context = Context({'posts': self.posts[:1]})
        rendered = template.render(context)
        
        self.assertIn('1 posts found', rendered)
        self.assertIn('Few posts', rendered)
        
        # Test with no posts
        context = Context({'posts': []})
        rendered = template.render(context)
        
        self.assertIn('No posts', rendered)
        self.assertNotIn('posts found', rendered)
    
    def test_url_template_tag(self):
        """Test url template tag"""
        
        template_string = """
        <a href="{% url 'blog:post_detail' slug=post.slug %}">{{ post.title }}</a>
        <a href="{% url 'blog:post_list' %}">All Posts</a>
        """
        
        template = Template(template_string)
        context = Context({'post': self.posts[0]})
        
        rendered = template.render(context)
        
        # Check URLs are generated correctly
        self.assertIn(f'/blog/{self.posts[0].slug}/', rendered)
        self.assertIn('/blog/', rendered)
        self.assertIn(self.posts[0].title, rendered)
    
    def test_csrf_token_template_tag(self):
        """Test CSRF token template tag"""
        
        template_string = """
        <form method="post">
            {% csrf_token %}
            <input type="text" name="title">
        </form>
        """
        
        # Create request with CSRF token
        request = RequestFactory().get('/')
        
        template = Template(template_string)
        context = Context({'request': request})
        
        rendered = template.render(context)
        
        # Check CSRF token is included
        self.assertIn('csrfmiddlewaretoken', rendered)
        self.assertIn('type="hidden"', rendered)

Testing Custom Template Tags

# blog/templatetags/blog_tags.py
from django import template
from django.utils.safestring import mark_safe
from blog.models import BlogPost

register = template.Library()

@register.simple_tag
def recent_posts(count=5):
    """Return recent published posts"""
    return BlogPost.objects.filter(status='published').order_by('-created_at')[:count]

@register.inclusion_tag('blog/tags/post_list.html')
def show_recent_posts(count=5):
    """Render recent posts using template"""
    posts = BlogPost.objects.filter(status='published').order_by('-created_at')[:count]
    return {'posts': posts}

@register.filter
def truncate_words_html(value, arg):
    """Truncate HTML content to specified word count"""
    from django.utils.html import strip_tags
    
    words = strip_tags(value).split()
    if len(words) <= arg:
        return value
    
    truncated = ' '.join(words[:arg])
    return mark_safe(f'{truncated}...')

@register.filter
def reading_time(content):
    """Calculate reading time for content"""
    words = len(content.split())
    minutes = max(1, words // 200)  # 200 words per minute
    return f"{minutes} min read"

# tests.py
from django.template import Template, Context
from django.template.loader import get_template

class CustomTemplateTagTests(TestCase):
    """Test custom template tags"""
    
    def setUp(self):
        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.category = Category.objects.create(name='Tech', slug='tech')
        
        # Create posts with different dates
        from django.utils import timezone
        from datetime import timedelta
        
        for i in range(5):
            post = BlogPost.objects.create(
                title=f'Post {i}',
                content=f'Content for post {i}',
                author=self.user,
                category=self.category,
                status='published'
            )
            
            # Set different creation dates
            post.created_at = timezone.now() - timedelta(days=i)
            post.save()
    
    def test_recent_posts_simple_tag(self):
        """Test recent_posts simple tag"""
        
        template_string = """
        {% load blog_tags %}
        {% recent_posts 3 as posts %}
        {% for post in posts %}
            {{ post.title }}
        {% endfor %}
        """
        
        template = Template(template_string)
        rendered = template.render(Context())
        
        # Should show 3 most recent posts
        self.assertIn('Post 0', rendered)  # Most recent
        self.assertIn('Post 1', rendered)
        self.assertIn('Post 2', rendered)
        self.assertNotIn('Post 3', rendered)  # Older post
        self.assertNotIn('Post 4', rendered)  # Oldest post
    
    def test_recent_posts_default_count(self):
        """Test recent_posts tag with default count"""
        
        template_string = """
        {% load blog_tags %}
        {% recent_posts as posts %}
        {{ posts|length }}
        """
        
        template = Template(template_string)
        rendered = template.render(Context())
        
        # Should return 5 posts (all posts, default count is 5)
        self.assertIn('5', rendered)
    
    def test_show_recent_posts_inclusion_tag(self):
        """Test show_recent_posts inclusion tag"""
        
        # Create the inclusion template
        inclusion_template_content = """
        <div class="recent-posts">
            {% for post in posts %}
                <div class="post">{{ post.title }}</div>
            {% endfor %}
        </div>
        """
        
        # This would normally be in templates/blog/tags/post_list.html
        # For testing, we'll mock the template loading
        
        template_string = """
        {% load blog_tags %}
        {% show_recent_posts 2 %}
        """
        
        # Note: This test would require the actual template file to exist
        # In practice, you might mock the template loading or create test templates
        
    def test_truncate_words_html_filter(self):
        """Test truncate_words_html custom filter"""
        
        template_string = """
        {% load blog_tags %}
        {{ content|truncate_words_html:5 }}
        """
        
        long_content = "This is a very long piece of content that should be truncated after five words and then some more."
        
        template = Template(template_string)
        context = Context({'content': long_content})
        rendered = template.render(context)
        
        # Should truncate after 5 words
        self.assertIn('This is a very long...', rendered)
        self.assertNotIn('piece of content', rendered)
    
    def test_truncate_words_html_filter_short_content(self):
        """Test truncate_words_html filter with short content"""
        
        template_string = """
        {% load blog_tags %}
        {{ content|truncate_words_html:10 }}
        """
        
        short_content = "Short content here."
        
        template = Template(template_string)
        context = Context({'content': short_content})
        rendered = template.render(context)
        
        # Should not truncate short content
        self.assertEqual(rendered.strip(), short_content)
        self.assertNotIn('...', rendered)
    
    def test_reading_time_filter(self):
        """Test reading_time custom filter"""
        
        template_string = """
        {% load blog_tags %}
        {{ content|reading_time }}
        """
        
        template = Template(template_string)
        
        # Test short content (< 200 words)
        short_content = "Short content here."
        context = Context({'content': short_content})
        rendered = template.render(context)
        
        self.assertIn('1 min read', rendered)
        
        # Test long content (400 words)
        long_content = ' '.join(['word'] * 400)
        context = Context({'content': long_content})
        rendered = template.render(context)
        
        self.assertIn('2 min read', rendered)
    
    def test_custom_tag_with_invalid_arguments(self):
        """Test custom tag behavior with invalid arguments"""
        
        template_string = """
        {% load blog_tags %}
        {% recent_posts "invalid" as posts %}
        {{ posts|length }}
        """
        
        template = Template(template_string)
        
        # Should handle invalid argument gracefully
        try:
            rendered = template.render(Context())
            # Depending on implementation, might return default or empty
        except Exception as e:
            # Or might raise an exception
            self.assertIsInstance(e, (ValueError, TypeError))

Testing Template Filters

Testing Built-in Filters

class BuiltinFilterTests(TestCase):
    """Test built-in Django template filters"""
    
    def test_date_filter(self):
        """Test date filter formatting"""
        
        from django.utils import timezone
        
        template_string = """
        {{ date_value|date:"Y-m-d" }}
        {{ date_value|date:"F j, Y" }}
        """
        
        test_date = timezone.datetime(2023, 12, 25, 10, 30, 0, tzinfo=timezone.utc)
        
        template = Template(template_string)
        context = Context({'date_value': test_date})
        rendered = template.render(context)
        
        self.assertIn('2023-12-25', rendered)
        self.assertIn('December 25, 2023', rendered)
    
    def test_length_filter(self):
        """Test length filter"""
        
        template_string = """
        {{ items|length }}
        {{ text|length }}
        """
        
        template = Template(template_string)
        context = Context({
            'items': [1, 2, 3, 4, 5],
            'text': 'Hello World'
        })
        rendered = template.render(context)
        
        self.assertIn('5', rendered)  # List length
        self.assertIn('11', rendered)  # String length
    
    def test_default_filter(self):
        """Test default filter"""
        
        template_string = """
        {{ value|default:"No value" }}
        {{ empty_value|default:"Empty" }}
        """
        
        template = Template(template_string)
        
        # Test with value
        context = Context({'value': 'Has value', 'empty_value': ''})
        rendered = template.render(context)
        
        self.assertIn('Has value', rendered)
        self.assertIn('Empty', rendered)
        
        # Test with None
        context = Context({'value': None, 'empty_value': None})
        rendered = template.render(context)
        
        self.assertIn('No value', rendered)
        self.assertIn('Empty', rendered)
    
    def test_safe_filter(self):
        """Test safe filter for HTML content"""
        
        template_string = """
        {{ html_content|safe }}
        {{ html_content }}
        """
        
        html_content = '<strong>Bold text</strong>'
        
        template = Template(template_string)
        context = Context({'html_content': html_content})
        rendered = template.render(context)
        
        # Safe version should render HTML
        self.assertIn('<strong>Bold text</strong>', rendered)
        # Unsafe version should escape HTML
        self.assertIn('&lt;strong&gt;Bold text&lt;/strong&gt;', rendered)
    
    def test_slice_filter(self):
        """Test slice filter"""
        
        template_string = """
        {{ items|slice:":3" }}
        {{ items|slice:"2:" }}
        """
        
        items = ['a', 'b', 'c', 'd', 'e']
        
        template = Template(template_string)
        context = Context({'items': items})
        rendered = template.render(context)
        
        # First 3 items
        self.assertIn("['a', 'b', 'c']", rendered)
        # Items from index 2
        self.assertIn("['c', 'd', 'e']", rendered)

Testing Custom Filters

# Additional custom filters for testing
@register.filter
def multiply(value, arg):
    """Multiply value by argument"""
    try:
        return float(value) * float(arg)
    except (ValueError, TypeError):
        return 0

@register.filter
def add_class(field, css_class):
    """Add CSS class to form field"""
    return field.as_widget(attrs={'class': css_class})

@register.filter
def currency(value):
    """Format value as currency"""
    try:
        return f"${float(value):.2f}"
    except (ValueError, TypeError):
        return "$0.00"

class CustomFilterTests(TestCase):
    """Test custom template filters"""
    
    def test_multiply_filter(self):
        """Test multiply custom filter"""
        
        template_string = """
        {% load blog_tags %}
        {{ value|multiply:3 }}
        {{ price|multiply:1.08 }}
        """
        
        template = Template(template_string)
        context = Context({'value': 10, 'price': 100})
        rendered = template.render(context)
        
        self.assertIn('30.0', rendered)  # 10 * 3
        self.assertIn('108.0', rendered)  # 100 * 1.08
    
    def test_multiply_filter_invalid_input(self):
        """Test multiply filter with invalid input"""
        
        template_string = """
        {% load blog_tags %}
        {{ invalid|multiply:3 }}
        """
        
        template = Template(template_string)
        context = Context({'invalid': 'not_a_number'})
        rendered = template.render(context)
        
        self.assertIn('0', rendered)  # Should return 0 for invalid input
    
    def test_currency_filter(self):
        """Test currency custom filter"""
        
        template_string = """
        {% load blog_tags %}
        {{ price1|currency }}
        {{ price2|currency }}
        {{ invalid|currency }}
        """
        
        template = Template(template_string)
        context = Context({
            'price1': 19.99,
            'price2': 100,
            'invalid': 'not_a_price'
        })
        rendered = template.render(context)
        
        self.assertIn('$19.99', rendered)
        self.assertIn('$100.00', rendered)
        self.assertIn('$0.00', rendered)  # Invalid input
    
    def test_add_class_filter_with_form_field(self):
        """Test add_class filter with form field"""
        
        from django import forms
        
        class TestForm(forms.Form):
            name = forms.CharField(max_length=100)
        
        template_string = """
        {% load blog_tags %}
        {{ form.name|add_class:"form-control" }}
        """
        
        form = TestForm()
        
        template = Template(template_string)
        context = Context({'form': form})
        rendered = template.render(context)
        
        self.assertIn('class="form-control"', rendered)
        self.assertIn('name="name"', rendered)

Testing Template Inheritance

Testing Template Blocks and Extends

class TemplateInheritanceTests(TestCase):
    """Test template inheritance"""
    
    def test_template_extends_base(self):
        """Test template extends base template"""
        
        # Base template content
        base_template = """
        <!DOCTYPE html>
        <html>
        <head>
            <title>{% block title %}Default Title{% endblock %}</title>
        </head>
        <body>
            <header>{% block header %}Default Header{% endblock %}</header>
            <main>{% block content %}{% endblock %}</main>
            <footer>{% block footer %}Default Footer{% endblock %}</footer>
        </body>
        </html>
        """
        
        # Child template content
        child_template = """
        {% extends "base.html" %}
        
        {% block title %}Custom Page Title{% endblock %}
        
        {% block content %}
            <h1>Page Content</h1>
            <p>This is the main content.</p>
        {% endblock %}
        """
        
        # In a real test, you'd have these as actual template files
        # For this example, we'll test the concept
        
        # Test that child template overrides blocks correctly
        template = Template(child_template)
        
        # This would require proper template loading setup
        # The test would verify that:
        # - Title block is overridden
        # - Content block is filled
        # - Header and footer use defaults
    
    def test_template_block_super(self):
        """Test template block.super functionality"""
        
        # Base template with block
        base_content = """
        {% block content %}
            <div class="base-content">Base content</div>
        {% endblock %}
        """
        
        # Child template extending base content
        child_content = """
        {% extends "base.html" %}
        
        {% block content %}
            {{ block.super }}
            <div class="child-content">Additional content</div>
        {% endblock %}
        """
        
        # Test would verify both base and child content appear
        # This requires proper template file setup
    
    def test_template_include(self):
        """Test template include functionality"""
        
        # Main template
        main_template = """
        <div class="page">
            {% include "partials/header.html" %}
            <main>{{ content }}</main>
            {% include "partials/footer.html" %}
        </div>
        """
        
        # Test that included templates are rendered
        # This requires actual template files
        template = Template(main_template)
        context = Context({'content': 'Main page content'})
        
        # Would test that included content appears in output

Testing Template Performance

Testing Template Rendering Performance

import time
from django.test import TestCase
from django.template import Template, Context

class TemplatePerformanceTests(TestCase):
    """Test template rendering performance"""
    
    def setUp(self):
        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.category = Category.objects.create(name='Tech', slug='tech')
        
        # Create many posts for performance testing
        self.posts = []
        for i in range(100):
            post = BlogPost.objects.create(
                title=f'Post {i}',
                content=f'Content for post {i}' * 50,  # Longer content
                author=self.user,
                category=self.category,
                status='published'
            )
            self.posts.append(post)
    
    def test_template_rendering_performance(self):
        """Test template rendering performance with many objects"""
        
        template_string = """
        {% for post in posts %}
            <article>
                <h2>{{ post.title }}</h2>
                <p>By {{ post.author.username }} in {{ post.category.name }}</p>
                <div>{{ post.content|truncatewords:50 }}</div>
                <time>{{ post.created_at|date:"F j, Y" }}</time>
            </article>
        {% endfor %}
        """
        
        template = Template(template_string)
        context = Context({'posts': self.posts})
        
        # Measure rendering time
        start_time = time.time()
        rendered = template.render(context)
        end_time = time.time()
        
        render_time = end_time - start_time
        
        # Assert reasonable performance (adjust threshold as needed)
        self.assertLess(render_time, 1.0, "Template rendering took too long")
        
        # Verify content was rendered
        self.assertIn('Post 0', rendered)
        self.assertIn('Post 99', rendered)
    
    def test_template_caching_effectiveness(self):
        """Test template caching improves performance"""
        
        template_string = """
        {% load cache %}
        {% cache 300 post_list %}
            {% for post in posts %}
                <div>{{ post.title }} - {{ post.created_at }}</div>
            {% endfor %}
        {% endcache %}
        """
        
        template = Template(template_string)
        context = Context({'posts': self.posts[:10]})  # Smaller set for caching test
        
        # First render (should cache)
        start_time = time.time()
        first_render = template.render(context)
        first_time = time.time() - start_time
        
        # Second render (should use cache)
        start_time = time.time()
        second_render = template.render(context)
        second_time = time.time() - start_time
        
        # Cached render should be faster
        self.assertLess(second_time, first_time)
        
        # Content should be identical
        self.assertEqual(first_render, second_render)

Testing Template Security

Testing Template Auto-escaping

class TemplateSecurityTests(TestCase):
    """Test template security features"""
    
    def test_auto_escaping_enabled(self):
        """Test that auto-escaping is enabled by default"""
        
        template_string = """
        {{ user_input }}
        """
        
        malicious_input = '<script>alert("XSS")</script>'
        
        template = Template(template_string)
        context = Context({'user_input': malicious_input})
        rendered = template.render(context)
        
        # Should escape HTML
        self.assertIn('&lt;script&gt;', rendered)
        self.assertNotIn('<script>', rendered)
    
    def test_safe_filter_bypasses_escaping(self):
        """Test that safe filter bypasses auto-escaping"""
        
        template_string = """
        {{ trusted_html|safe }}
        """
        
        trusted_html = '<strong>Bold text</strong>'
        
        template = Template(template_string)
        context = Context({'trusted_html': trusted_html})
        rendered = template.render(context)
        
        # Should not escape trusted HTML
        self.assertIn('<strong>Bold text</strong>', rendered)
        self.assertNotIn('&lt;strong&gt;', rendered)
    
    def test_autoescape_tag_control(self):
        """Test autoescape tag for controlling escaping"""
        
        template_string = """
        {% autoescape off %}
            {{ user_input }}
        {% endautoescape %}
        
        {% autoescape on %}
            {{ user_input }}
        {% endautoescape %}
        """
        
        html_input = '<em>Emphasized text</em>'
        
        template = Template(template_string)
        context = Context({'user_input': html_input})
        rendered = template.render(context)
        
        # First part should not escape (autoescape off)
        self.assertIn('<em>Emphasized text</em>', rendered)
        
        # Second part should escape (autoescape on)
        self.assertIn('&lt;em&gt;Emphasized text&lt;/em&gt;', rendered)
    
    def test_template_prevents_code_injection(self):
        """Test template prevents code injection"""
        
        # Malicious template code
        template_string = """
        {{ user_input }}
        """
        
        # Attempt to inject template code
        malicious_input = '{% load os %}{% os.system("rm -rf /") %}'
        
        template = Template(template_string)
        context = Context({'user_input': malicious_input})
        rendered = template.render(context)
        
        # Should render as text, not execute as template code
        self.assertIn('{% load os %}', rendered)
        # Should not actually execute the command

Next Steps

With comprehensive template testing in place, you're ready to move on to testing authentication. The next chapter will cover testing Django's authentication system, including user registration, login/logout, permissions, and custom authentication backends.

Key template testing concepts covered:

  • Basic template rendering and context testing
  • Custom template tags and filters testing
  • Template inheritance and inclusion testing
  • Template performance testing
  • Template security and auto-escaping testing

Template tests ensure your application's presentation layer works correctly, displays the right content, and maintains security standards while providing a good user experience.