Testing Batteries

djxi.testing provides utilities that make it straightforward to write unit and integration tests for DXEndpointBattery classes.

Overview

Name

Description

DXRequestFactory

RequestFactory wrapper that applies the full MIDDLEWARE stack, so request.htmx and other middleware-set attributes are available.

assert_section_rendered(response, text)

Standalone assertion: raises AssertionError if text is not in the response content.

assert_section_not_rendered(response, text)

Standalone assertion: raises AssertionError if text is in the response content.

DXBatteryTestCase

django.test.TestCase subclass with self.dx (DXRequestFactory) pre-built and both assertion helpers as instance methods.

DXBatteryTestCase

The recommended base class for battery tests:

# myapp/tests.py
from djxi.testing import DXBatteryTestCase
from myapp.views import ItemBattery

class ItemBatteryTests(DXBatteryTestCase):

    def test_list_renders_empty_state(self):
        request = self.dx.get("/")
        response = ItemBattery().render_section(request, "item-list", {"items": []})
        self.assert_section_rendered(response, "No items yet.")

    def test_list_renders_item_title(self):
        item = Item.objects.create(title="Buy milk")
        request = self.dx.get("/")
        response = ItemBattery().render_section(
            request, "item-list", {"items": [item]}
        )
        self.assert_section_rendered(response, "Buy milk")
        self.assert_section_not_rendered(response, "No items yet.")

    def test_create_adds_item(self):
        request = self.dx.post("/", data={"title": "New task"})
        response = ItemBattery().url_patterns()[1].callback(request)
        self.assertEqual(response.status_code, 200)
        self.assertTrue(Item.objects.filter(title="New task").exists())

DXRequestFactory

DXRequestFactory mirrors the RequestFactory API (get, post, put, patch, delete) but runs the full MIDDLEWARE stack before returning the request:

from djxi.testing import DXRequestFactory

factory = DXRequestFactory()

request = factory.get("/items/")
assert hasattr(request, "htmx")   # DjxiHeadersMiddleware has run

request = factory.post("/items/create", data={"title": "Task"})
assert request.POST["title"] == "Task"

You can pass a custom middleware list to isolate specific middleware:

factory = DXRequestFactory(middleware=["djxi.middleware.DjxiHeadersMiddleware"])
request = factory.get("/")
assert hasattr(request, "htmx")

Standalone assertion helpers

Import and use them in any test framework (pytest, unittest, etc.):

import pytest
from django.http import HttpResponse
from djxi.testing import assert_section_rendered, assert_section_not_rendered

def test_greeting():
    response = HttpResponse(b"<p>Hello, Alice!</p>")
    assert_section_rendered(response, "Hello, Alice!")
    assert_section_not_rendered(response, "Hello, Bob!")

def test_greeting_fails_correctly():
    response = HttpResponse(b"<p>Nothing here</p>")
    with pytest.raises(AssertionError) as exc_info:
        assert_section_rendered(response, "Hello, Alice!")
    assert "Hello, Alice!" in str(exc_info.value)

Testing async handlers

For async handlers, call the view function directly and await it:

import asyncio
from djxi.testing import DXRequestFactory
from myapp.views import AsyncItemBattery

def test_async_list():
    factory = DXRequestFactory()
    request = factory.get("/")
    patterns = AsyncItemBattery.url_patterns()
    view = next(p for p in patterns if p.name == "list").callback

    response = asyncio.get_event_loop().run_until_complete(view(request))
    assert response.status_code == 200
    assert b"item-list" in response.content

Testing permission hooks

Attach a user object to the request to test requires_auth and check_permissions():

from types import SimpleNamespace
from django.contrib.auth.models import AnonymousUser

def test_anonymous_is_rejected():
    factory = DXRequestFactory()
    request = factory.get("/")
    request.user = AnonymousUser()

    view = ProtectedBattery.url_patterns()[0].callback
    response = view(request)
    assert response.status_code == 403

def test_authenticated_user_is_allowed():
    factory = DXRequestFactory()
    request = factory.get("/")
    request.user = SimpleNamespace(is_authenticated=True)

    view = ProtectedBattery.url_patterns()[0].callback
    response = view(request)
    assert response.status_code == 200

API reference

class DXRequestFactory(middleware=None)
Parameters:

middleware – Optional list of middleware dotted paths. Defaults to settings.MIDDLEWARE.

get(path='/', **kwargs)
post(path='/', data=None, **kwargs)
put(path='/', data=None, **kwargs)
patch(path='/', data=None, **kwargs)
delete(path='/', **kwargs)

Create an HttpRequest and run it through the middleware stack.

assert_section_rendered(response, text)

Assert that text is present in the decoded response body. Raises AssertionError with a descriptive message on failure.

assert_section_not_rendered(response, text)

Assert that text is absent from the decoded response body. Raises AssertionError with a descriptive message on failure.

class DXBatteryTestCase

Subclass of django.test.TestCase with:

self.dx — a pre-built DXRequestFactory.

assert_section_rendered(response, text)
assert_section_not_rendered(response, text)

Delegate to the standalone helpers above.