Testing Batteries ================= ``djxi.testing`` provides utilities that make it straightforward to write unit and integration tests for ``DXEndpointBattery`` classes. Overview -------- .. list-table:: :header-rows: 1 :widths: 35 65 * - 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: .. code-block:: python # 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: .. code-block:: python 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: .. code-block:: python 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.): .. code-block:: python import pytest from django.http import HttpResponse from djxi.testing import assert_section_rendered, assert_section_not_rendered def test_greeting(): response = HttpResponse(b"
Hello, Alice!
") assert_section_rendered(response, "Hello, Alice!") assert_section_not_rendered(response, "Hello, Bob!") def test_greeting_fails_correctly(): response = HttpResponse(b"Nothing here
") 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: .. code-block:: python 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()``: .. code-block:: python 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 ------------- .. py:class:: DXRequestFactory(middleware=None) :param middleware: Optional list of middleware dotted paths. Defaults to ``settings.MIDDLEWARE``. .. py:method:: get(path="/", **kwargs) .. py:method:: post(path="/", data=None, **kwargs) .. py:method:: put(path="/", data=None, **kwargs) .. py:method:: patch(path="/", data=None, **kwargs) .. py:method:: delete(path="/", **kwargs) Create an ``HttpRequest`` and run it through the middleware stack. .. py:function:: assert_section_rendered(response, text) Assert that *text* is present in the decoded response body. Raises ``AssertionError`` with a descriptive message on failure. .. py:function:: assert_section_not_rendered(response, text) Assert that *text* is absent from the decoded response body. Raises ``AssertionError`` with a descriptive message on failure. .. py:class:: DXBatteryTestCase Subclass of ``django.test.TestCase`` with: ``self.dx`` — a pre-built ``DXRequestFactory``. .. py:method:: assert_section_rendered(response, text) .. py:method:: assert_section_not_rendered(response, text) Delegate to the standalone helpers above.