wolfpigeon
wolfpigeon

Reputation: 143

Django tests are not outputting Messages in template rendering

I can't seem to get a unit test to check if Messages are rendering properly in my template. As per my template, I'm not getting any output where the messages should be listed.

I'm using pytest 6.2.5 and Django 3.1.13 if that's helpful. Here is my test code:

import pytest
from django.contrib import messages
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import RequestFactory
from django.views.generic import TemplateView

pytestmark = pytest.mark.django_db

class TestBaseTemplate:
    def test_messages_middleware(self):
        request = RequestFactory().get("/")
        view = TemplateView.as_view(template_name="base.html")

        middleware1 = SessionMiddleware()
        middleware1.process_request(request)
        request.session.save()

        middleware2 = MessageMiddleware()
        middleware2.process_request(request)

        foobar_msg = "Hello, world!"
        messages.info(request, foobar_msg)

        request.session.save()
        response = view(request)

        response.render()
        content = response.content.decode("utf-8")

        # This assert fails
        assert foobar_msg in content 

My message block in the template (base.html) is straight-forward (and does work in views that send messages):

  {% if messages %}
  <div>
    {% for message in messages %}
    <p>{{ message }}</p>
    {% endfor %}
  </div>
  {% endif %}

I can see through debugging that before you get to rendering the response that the "Hello, world!" message is being noted properly in the request via the Session/Message middleware, it's just not being rendered in the response.

Any ideas on what I'm missing? Do I need to do something with a context preprocessor before rendering the content?

Thanks in advance for any advice!

Upvotes: 0

Views: 505

Answers (1)

wolfpigeon
wolfpigeon

Reputation: 143

There might be a more straight-forward method, but the trick for me was to explicitly include the messages in the view context.

Here is how I managed to get the view properly rendering messages. Note that I'm using pytest, with a standard RequestFactory fixture. It should be pretty trivial to convert this code to a unittest paradigm.

from typing import Any

import pytest
from django.contrib import messages
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import RequestFactory
from django.views.generic import TemplateView


pytestmark = pytest.mark.django_db


class TestTemplateMessages:
    """Tests for the messages rendering in template."""

    class SampleTemplate(TemplateView):
        """Template for testing."""

        template_name = "frame_with_messages_block.html"

        def __init__(self, **kwargs: Any) -> None:
            """Setup request middleware."""
            super().__init__(**kwargs)
            middleware1 = SessionMiddleware()
            middleware1.process_request(self.request)

            middleware2 = MessageMiddleware()
            middleware2.process_request(self.request)

            self.request.session.save()

        def get_context_data(self, **kwargs: str) -> dict[str, Any]:
            """Add current user to context."""
            context = super().get_context_data(**kwargs)
            context["messages"] = messages.get_messages(self.request)
            return context

    def test_messages_middleware(self, rf: RequestFactory) -> None:
        """Test messages are rendering properly in templates."""
        request = rf.get("/")

        info_message = "Hello, world!"
        error_message = "Hello, error?"
        expected_messages = [info_message, error_message]

        view = self.SampleTemplate(request=request)
        messages.info(request, info_message)
        messages.error(request, error_message)

        # Test messages are part of the request middleware
        actual_messages = [str(m) for m in messages.get_messages(request)]
        assert actual_messages == expected_messages

        # Test messages are present in the response HTML
        response = view.get(request)
        response.render()  # type: ignore
        content = response.content.decode("utf-8")
        for message in expected_messages:
            assert message in content

Upvotes: 1

Related Questions