carltongibson / django-template-partials

Reusable named inline partials for the Django Template Language.
MIT License
428 stars 15 forks source link

Add instrumented test rendering to partials. #54

Open hendrik1120 opened 3 weeks ago

hendrik1120 commented 3 weeks ago

Hello, thank you for creating this app. I have been playing around with it for a few days now, but I am unable to test the returned context from a view when rendering a template partial.

The view function looks like this:

@login_required
@require_http_methods(["GET"])
def get_alarms(request: HttpRequest):
    template_name = "pages/alarms.html#alarm_list"
    if request.htmx:
        alarm_list = Alarm.objects.prefetch_related("vehicles").order_by("-ts")
        alarm_filter = AlarmFilter(request.GET, queryset=alarm_list)
        paginator = Paginator(alarm_filter.qs, 30)
        page = request.GET.get("page", 1)
        try:
            alarms = paginator.page(page)
        except PageNotAnInteger:
            alarms = paginator.page(1)
        except EmptyPage:
            alarms = paginator.page(paginator.num_pages)
        context = {"alarms": alarms}
        return render(request, template_name, context)
    raise Http404

The test looks like this:

@pytest.mark.django_db
def test_get_alarms_pagination(client, test_user):
    # Create some Alarm objects
    num_alarms = 100
    AlarmFactory.create_batch(num_alarms)
    # unauthorized
    response = client.get(reverse("get-alarms"))
    assert response.status_code == 302
    # authorized
    client.force_login(test_user)
    # Define the page size
    page_size = 30
    # Test the first page
    response = client.get(reverse("get-alarms") + "?page=1", HTTP_HX_REQUEST="true")
    assert response.status_code == 200
    assert len(response.context["alarms"]) == page_size
    ...

The test will always fail with this error:

>       assert len(response.context["alarms"]) == page_size
E       TypeError: 'NoneType' object is not subscriptable

Which means that reponse.context is always None when running the test. If I change the template from "pages/alarms.html#alarm_list" to e.g., "base.html" the test passes just fine. The view works just fine in the browser, this just occurs during testing.

I am not really sure if this is a bug, please let me know if there is a better way to test this.

Edit: I installed template-partials by just adding it to INSTALLED_APPS without the advanced setup.

hendrik1120 commented 3 weeks ago

I created a demo app to replicate this issue with minimal dependencies: https://github.com/hendrik1120/test_partials The tests also fail using only the django built in tests, pytest and pytest-django are optional.

CleitonDeLima commented 3 weeks ago

I believe it is because of performance as shown here. I also had the need to analyze the context and I couldn't.

hendrik1120 commented 3 weeks ago

I think that only applies if you are using a partial in a for loop or using the with tag to modify the context. This must be unintentional, as returning the current context shouldn't be expensive at all. Otherwise, this would be a huge dealbreaker and should be at the top of the README.

carltongibson commented 3 weeks ago

Hi @hendrik1120 — thanks for the report. Yes, partials aren't currently firing the template_rendered signal during test runs, that the Django test runner patches into Template itself.

This means (in particular) that the context and templates attributes that the test client adds will be missing.

Until we can merge into Django (refs #29) it's likely a test runner subclass overriding the appropriate hook would be needed here. Would you fancy having a look at that?

hendrik1120 commented 3 weeks ago

Hi, thanks for the explanation. To be honest, that's quite a bit above my django skill level currently. Looking forward to this getting merged into Django.

carltongibson commented 3 weeks ago

@hendrik1120 For the moment I'd suggest just asserting the response content, which has what you're expecting. HTH.