I've created a Django application using HTMX. Usually I'd jump to React or Vue. It was a great exercise and I can see where HTMX would be a great fit.
1. If you consider yourself a "backend developer" only and want to write the minimum amount of JS/TS.
2. If your application is simple. Yes you can do more complicated interactivity with oob swaps, but you end up with more complexity than you chose HTMX for.
For my next projects, I will still use Django but with React/Vue for pieces that need interactivity. As an example in my HTMX app, I wanted to add a profile image uploader. Lots of great frontend libraries exist to resize / modify your image before even uploading it to your server.
Finally, HTMX is just not testable the same way modern frontend libraries are. I've managed to write some decent tests using beautifulsoup, but it's night and day compared to vitest or jest.
This was my exact experience - loved it as a backend developer wanting to build simple frontends with minimal JavaScript (and I still recommend it for many use cases) but when I then tried HTMX to build a more complex application I found that the complexity that would normally be handled by a JavaScript framework ultimately grew and shifted to the HTML and, surprisingly, the backend as well.
For example, error handling in complex forms. When you have React handling errors you simply return a 400 JSON response with fields and reasons, or some other error message, and React decides what to do depending on the context, which it knows. When this responsibility is moved to the backend you now need to track the request location, state etc. and find the right fragment, which very quickly turned the code into a mess.
I think the choice of backend is also important. I suspect that Java (Spring Boot) made using HTMX much less pleasant than it would have been with something else, especially when it comes to templates and routing. Using Java for HTML templates (Thymeleaf or any of the other popular libraries) is something I will never do again.
Edit:
The analogy with jQuery is interesting. I feel the same way about HTMX now as I did about jQuery ten years ago. I used to plug jQuery into every project even if it was just to select elements or whatever, but whenever I used jQuery entirely over the frameworks that were popular at the time (Ember, Angular) I ended up with way messier code.
> When this responsibility is moved to the backend you now need to track the request location, state etc. and find the right fragment,
Maybe not? I'm just returning a 422 JSON response with fields and reasons and I have a tiny bit of JavaScript in the frontend that just plugs them in to the form with the built-in Constraint Validation API.
> I suspect that Java (Spring Boot) made using HTMX much less pleasant than it would have been with something else, especially when it comes to templates and routing.
IMHO one of the biggest advantages of using HTMX is not having to maintain three layers of test suites (backend, frontend, end-to-end). You just need to test that your endpoints return the right HTML piece. You don't need unit testing the frontend since you don't have that much logic there. Just rely on end-to-end tests as a complementary tool to check small pieces of logic on the HTMX code.
I’m not sure I’d agree. The HX attributes encode a lot of functionality and you want to check that they do the right thing together. You can test you get the right attributes in a response, but that’s only a proxy for the application doing the right thing, so you most likely still need the end to end tests.
The problem is that end to end tests are the hardest kind to write. At least if you have a frontend code base you can have some tests there and get some confidence about things working at a lower level.
> You just need to test that your endpoints return the right HTML piece. You don't need unit testing the frontend since you don't have that much logic there.
Only using the standard Django test client? I don't find this myself when I've had to work with HTMX code e.g. if you're using HTMX for your sign up form, it can be completely broken on the happy path and on form errors if the HTMX attributes are wrong, and the standard Django test client won't let you know, so you lose a lot of confidence when making refactors/changes.
That honestly sounds like a downside. Having to verify HTML as an output (in comparison to e.g. JSON) is brittle since it will often change just for presentation reasons. Having a clear and somewhat stable API (which is a boon for testing) is IMHO a strong reason for the SPA model.
> Having to verify HTML as an output (in comparison to e.g. JSON) is brittle since it will often change just for presentation reasons.
For HTML tests, if you target elements by their ARIA role, semantic tag, or by matching against the text/label/title the user would see, it should be robust to a lot of presentation changes (and does some accessibility checks too). It's much more robust than e.g. `body > div > div .card > h2` which are sure to break in tests and slow you down when you make changes later.
Not sure if this is what you meant, but you can't rely on only the JSON because you can't assume it's been properly transformed into the expected HTML.
In my experience snapshots have largely fallen out of vogue, at least as far as them blocking deploys goes. They're usually flaky, change often, are slow to test and it's way too easy to have false positives which lulls people into complacency and just regenerating the snapshots as soon as they reach a failure. I'm guilty of that myself, unless I can spot the breakage immediately I pretty much just regen the snapshot by default subconsciously because of how often I've had to do that for false positives
Then what's to stop you from making any unit test pass by just changing the expectations? The best testing technique in the world can't save us from developer error.
The problem with snapshot tests is that they tend to fail when something is changed.
One property of good tests is that they only fail when something is broken.
Snapshot tests aren't really automated tests, because you have to manually check the outputs to see if failures are genuine. It's a reminder to do manual checking but it's still not an automated process.
> The best testing technique in the world can't save us from developer error.
Sure, but using a testing technique that increases developer error is unwise, and snapshot testing has done that every time I've seen it used, so I don't use it anymore.
Then fix the test so that it fails only when something breaks? Do people not fix flaky, overly broad, or incorrect unit tests? How is a snapshot test any different?
I'm not sure you understand what snapshot testing is. It's a form of testing where you render frontend components and take a "snapshot", storing the output and saving it for later, then on the next test run, the component is rendered again and compared to the saved snapshot.
Any change that modifies the rendering of any component under test will break snapshot tests. It's literally just a test that says, "Is this render function still the same as it used to be?"
I'm not sure you understand how to capture snapshots at the correct level of granularity. If you have snapshots that are breaking for irrelevant changes, then by definition those changes don't need to be snapshotted, do they? Cut down the snapshot to only the relevant part of the rendered HTML so that when it fails, you know something broke.
It would still break if you need to modify that output in any way. Need to add a class for styling? The snapshot breaks. Need to add a new hx-attribute? The snapshot breaks. It's not tied to the logic, it's tied to whatever markup you output. You've reduced the surface area of the problem, but not eliminated it.
> If you have snapshots that are breaking for irrelevant changes, then by definition those changes don't need to be snapshotted, do they?
How often? All the time? Or relatively rarely? For most projects, it's the latter. We are not constantly churning the styling.
> Need to add a new hx-attribute? The snapshot breaks. It's not tied to the logic
An `hx-` attribute is logic. That's the point of htmx.
> You've reduced the surface area of the problem, but not eliminated it.
That's a risk of any kind of unit test. You can always have false positives.
> You're so close to getting it.
That tests should be fixed until they're robust? I'm not a fan of the learned helplessness of the 'We couldn't figure out how to do XYZ, therefore XYZ is a bad idea' approach.
Do you have experience with snapshot tests? If not, you should try it out for a bit and see what we mean, you'll quickly run into the situation we described.
If you introduce a new feature and have a new button on the page, for example, the snapshot test will fail (by necessity, you're changing what's on the page) because the snapshot doesn't match the current version with the new button, for example. So, what you have to do is regenerate the snapshot (regenerating it will always make it pass).
The problem is in my experience, that these kind of tests are massively unreliable. So you either fiddle with tolerances (most testing libraries let you set a threshold for what constitutes a failure in a snapshot) a lot, or you manually have to hunt down and pixel peek at what might've happened.
As an example of what usually happens is that, at some middle step of the test an assert fails because some new code causes things to render slightly out-of-order or delayed in some way. Ultimately the final product is good and if it was an intelligent system it would give it a pass. The unit tests pass. Even the integration and E2E tests pass, but the snapshot tests fail because something changed. The chance that this test failure is a false positive is much, much higher than the chance of it being a legitimate failure, but let's assume it's legitimate like the good citizens we are and try to hunt it down.
So, you go through the snapshots, 1-by-1, setting breakpoints where you think things might break - note that this process by itself is painful, slow and manual, and often running snapshot tests on your local machine can break things because things get rendered differently than how they would in CI, which leads to an extra set of headaches - What happens here is that you're testing an imperfect reflection of what the actual underlying app is, though, because a lot or most of it will be mocked/hoisted/whatever. So sifting through what is a potential breakage, and what is just the quirk of the snapshot test is basically a gamble at best. You spin up the app locally and see that nothing looks out of the ordinary. No errors to be seen. Things look and behave as they should. You have your other tests, and they all pass. So, you chalk it up to a false positive, regen the snapshots, and go on with your day.
The next day you add some other thing to the page. Uh oh, snapshots fail again!
Rinse and repeat like this, and after the 50th time sitting there waiting for the snapshot to be captured, you decide there are better ways to spend your limited time on this mortal plain and just say "Fuck it, this one's probably a false positive as well" after taking a 2 minute look at the page locally before hitting the repacture command again.
I legitimately don't think I've ever actually seen a non-false positive snapshot test failure, to be honest.
Snapshot tests are even more sensitive to flakiness than E2E tests but flakiness is still a bug that can (and probably should) be fixed like any other.
As an example - the different browser you have running locally and in CI? That's a bug. The fix is to run the browser in a container - the same container in both environments. You can probably get away without this with an E2E test. Snapshot test? No way.
Ive seen people decry and abandon E2E tests for the same reason - simply because they couldnt get flakiness under control and thought that it was impossible to do so.
I dont think it's intrinsically impossible though. I think it's just a hard engineering problem. There are lots of nonobvious points of flakiness - e.g. unpinned versions, selects without order bys, nondeterministic JavaScript code, etc. but few that are simply IMpossible to fix.
> 1. If you consider yourself a "backend developer" only and want to write the minimum amount of JS/TS.
This is a great point and should be underscored more. I think generally the HTMX vs React/Vue/Svelte argument gets undercut when people don't express their innate bias on which part of the stack they want to give more control to. HTMX is great if you're pro-expanding the server; JS Frameworks if you're trying to expand it from the application layer.
With the BeautifulSoup tests I can run 1000s of tests in seconds. Cypress/Playwright are wonderful for e2e tests, but not comparable to simple unit tests you'd have in React/Vue. I find that the BeautifulSoup tests are a decent middle ground.
But with beautifulsoup you don't test the interactions at all. You only test the rendered template which is nice, but doesn't tell you if any button actually works the way you expect.
I've successfully used (for 3+ years in multiple projects) an approach similar to BeautifulSoup's get_text() function to assert the whole visible text of a component. You can implement it in other languages with a few regexes.
One addition makes it an order of magnitude more versatile: a way to visualize non-textual information as text in the tests. For example add a data-test-icon attribute which contains the replacement text that should be shown in tests. Another method is to parse the HTML and write custom visualization for some elements, so that you get useful default visualization for at least form elements and such, without having to repeat the data-test-icon attribute every time.
They're both fantastic expansions of what server-side rendering can do. They can't really do work as nice as what React can, but you can get perhaps 85% of React for 5-10% of the development time. And a big increase in usability of your app.
As I dont do frontend work, how do you "test" anything without an insane amount of browsers/hardware realistically ?
If I was to write tests, it'd be basically http get/post/put/delete requests and measuring the responses are what I expect.. how can HTMX be any different here ?
How do you "test" a button works without a browser engine ? Every browser engine ?
but they're about as reliable as you can expect: it's difficult to keep up the pace with the big three (or two) on standards compliance, and they usually don't even try.
So the only reliable solution is a headless Chromium, Firefox, and/or WebKit-based noname browser like the sibling says.
1. If you consider yourself a "backend developer" only and want to write the minimum amount of JS/TS.
2. If your application is simple. Yes you can do more complicated interactivity with oob swaps, but you end up with more complexity than you chose HTMX for.
For my next projects, I will still use Django but with React/Vue for pieces that need interactivity. As an example in my HTMX app, I wanted to add a profile image uploader. Lots of great frontend libraries exist to resize / modify your image before even uploading it to your server.
Finally, HTMX is just not testable the same way modern frontend libraries are. I've managed to write some decent tests using beautifulsoup, but it's night and day compared to vitest or jest.