Skip to content

Add HtmxClient and testing assertions (Fixes #583)#589

Closed
alkapandey1031986-arch wants to merge 4 commits intoadamchainz:mainfrom
alkapandey1031986-arch:feature/issue-583-testing-utilities
Closed

Add HtmxClient and testing assertions (Fixes #583)#589
alkapandey1031986-arch wants to merge 4 commits intoadamchainz:mainfrom
alkapandey1031986-arch:feature/issue-583-testing-utilities

Conversation

@alkapandey1031986-arch
Copy link
Copy Markdown

Hello,

This PR resolves #583. It introduces an HtmxClient and HtmxTestCaseAssertions inside a new django_htmx.testing module. This eliminates the need for developers to manually build header scaffoldings to mimic HTMX requests.

A dedicated internal Pytest suite has been added and passes entirely.

@adamchainz
Copy link
Copy Markdown
Owner

Thank you for engaging on this issue. However, the value proposition here is very minimal: a client that sets hx-request to true, plus two very niche assertion methods (maybe too niche to bother using - assert response.status_code == HTMX_STOP_POLLING is clear enough, since we expose that constant). I don't think that's enough draw to ask users to switch their testing client.

I was thinking of something with more tooling to set htmx headers, like a client where all methods accept an htmx argument that can configure multiple headers succinctly, for example:

# Sets hx-request only:
response = self.client.get("/hot-dogs/", htmx=True)

# Sets hx-request and hx-target:
response = self.client.get("/hot-dogs/", htmx={"target": "#dogs"})

This is as far as my thinking has gone, as I've honestly not written many tests for htmx views myself. Have you?

@alkapandey1031986-arch alkapandey1031986-arch force-pushed the feature/issue-583-testing-utilities branch from 10a58ac to 5db6438 Compare April 15, 2026 05:11
@alkapandey1031986-arch
Copy link
Copy Markdown
Author

Thanks for the feedback, really appreciate it!

You're right — the assertion helpers were too thin. I've reworked the client based on your suggestion. It now accepts an htmx kwarg directly on request methods:

python

Sets HX-Request only

response = self.client.get("/hot-dogs/", htmx=True)

Sets HX-Request + HX-Target

response = self.client.get("/hot-dogs/", htmx={"target": "#dogs"})

Multiple headers at once

response = self.client.get("/hot-dogs/", htmx={"target": "#dogs", "trigger": "click"})

The dict keys mirror the property names on HtmxDetails (target, trigger, trigger_name, prompt, boosted, current_url, history_restore_request) so they feel natural to anyone already using this library. Passing an unknown key raises a clear ValueError. Removed the assertion helpers entirely.

As for writing htmx view tests myself — not extensively, but that's honestly what motivated this. Setting headers manually every time got old fast.

Happy to adjust anything!

@adamchainz
Copy link
Copy Markdown
Owner

As for writing htmx view tests myself — not extensively, but that's honestly what motivated this. Setting headers manually every time got old fast.

Yeah, fair enough!

@alkapandey1031986-arch alkapandey1031986-arch force-pushed the feature/issue-583-testing-utilities branch from 1d72f93 to 3d43d68 Compare April 16, 2026 06:02
@alkapandey1031986-arch
Copy link
Copy Markdown
Author

I also added a new documentation page (docs/testing.rst) to cover the HtmxClient usage at the main index. I figured a new module like this isn't much use to people if it isn't in the official docs!

I've also run the full pre-commit suite (ruff, mypy, etc.) locally to make sure everything matches your project's formatting exactly.

Ready for another look whenever you have a moment.

@alkapandey1031986-arch alkapandey1031986-arch deleted the feature/issue-583-testing-utilities branch April 16, 2026 07:19
alkapandey1031986-arch pushed a commit to alkapandey1031986-arch/django-htmx that referenced this pull request Apr 16, 2026
Comment on lines +39 to +41
if htmx is not None:
for key, value in _build_htmx_headers(htmx).items():
request.setdefault(key, value)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread tests/test_testing.py

@override_settings(ROOT_URLCONF=__name__)
class HtmxClientTests(SimpleTestCase):
# Basic: htmx=True just sets HX-Request
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of comment isn't needed

Suggested change
# Basic: htmx=True just sets HX-Request

Comment thread tests/test_testing.py
Comment on lines +14 to +23
def echo_view(request: Any) -> HttpResponse:
return HttpResponse(json.dumps(dict(request.headers)))


urlpatterns = [
path("echo/", echo_view),
]


@override_settings(ROOT_URLCONF=__name__)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you move the views and urls into tests.views and tests.urls?

Comment thread tests/test_testing.py
client = HtmxClient()
response = client.get("/echo/", htmx=True)

headers = json.loads(response.content)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use response.json() throughout the tests

alternatively, we can actually use response.wsgi_request.headers - no need for the view to echo the headers back to us!

Comment thread tests/test_testing.py
with pytest.raises(ValueError, match="Unknown htmx kwarg"):
client.get("/echo/", htmx={"typo_key": "bad"})

# No htmx kwarg at all means a normal non-HTMX request
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment dupes the test title

Suggested change
# No htmx kwarg at all means a normal non-HTMX request

Comment thread tests/test_testing.py
headers = json.loads(response.content)
assert "Hx-Request" not in headers

# Works with POST too, not just GET
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Works with POST too, not just GET

Comment thread tests/test_testing.py
response = client.get("/echo/", htmx=True)

headers = json.loads(response.content)
assert headers.get("Hx-Request") == "true"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use indexing for the headers, as you expect them to be there so it's fine to raise KeyError if they're not

Suggested change
assert headers.get("Hx-Request") == "true"
assert headers["Hx-Request"] == "true"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Testing utilities for htmx views

2 participants