# Feature request: new assertion \`assertNotInHTML\`, like \`assertContains\` has \`assertNotContains\`

**URL:** <https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711>\
**Category:** Django Internals\
**Created:** [June 19, 2023, 9:01am UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711 "2023-06-19T09:01:22Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![thibaudcolas](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/thibaudcolas/32/3956_2.png) [@thibaudcolas](https://forum.djangoproject.com/u/thibaudcolas)\
**Post date:** [June 19, 2023, 9:01am UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711/1 "2023-06-19T09:01:22Z")

</div>

Python unit tests have `assertIn`/`assertNotIn` for strings. Django has `assertContains`/`assertNotContains` with HTML-aware processing of responses. Django also has `assertInHTML` – but somehow there is no `assertNotInHTML`.

This creates confusion when writing Django unit tests – if I use `assertContains` and `assertNotContains` to test a response, I should be able to switch my test cases to `assertInHTML` / `assertNotInHTML` if I refactor my code so the tests are no longer for a whole response.

There are two sources of confusion for me here:

- Since `assertContains` has `assertNotContains` and `assertIn` has `assertNotIn` – given `assertInHTML`, I tend to assume `assertNotInHTML` exists, try to use it, and realise my mistake.
- Sometimes I work on projects that have a `assertNotInHTML`. That’s defined in a base test case class, so when I write unit tests I don’t realise this is a project-specific method – and when I go looking for its HTML-processing logic on [​assertions docs](https://docs.djangoproject.com/en/4.2/topics/testing/tools/#assertions), I’m surprised not to see it.

So how about a new `assertNotInHTML`?

---

<div class="post-metadata">

**Author:** ![thibaudcolas](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/thibaudcolas/32/3956_2.png) [@thibaudcolas](https://forum.djangoproject.com/u/thibaudcolas)\
**Post date:** [June 19, 2023, 9:05am UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711/2 "2023-06-19T09:05:27Z")

</div>

For ref, I mistakenly reported this on the issue tracker ([#34658](https://code.djangoproject.com/ticket/34658), sorry) and got two replies,

> I fully agree with the rationale, but for this, perhaps, Thibaud could you please create a forum post to allow for people to present any counter argument we may not be considering?  
> – Natalia Bidart

> I’m skeptic. Personally, I find the use of `*NotIn*` assertions really rare and not the best practice. There can many many reasons why something is not in something else, and it becomes even more unreliable when we do this in HTML. It’s much more stable and bulletproof to use `*In*` assertions. I don’t think it’s worth adding.  
> Mariusz Felisiak

* * *

@felixxm I’m 100% with you on `*NotIn*` assertions often being problematic. I try to avoid them in new code whenever possible. Working on an existing project I still find that `assertNotInHTML` has its place though:

- If I have to switch a test from `assertNotContains(…, html=True)` to something that’s not a response – it’s really valuable to be able to change the assertion and move on. Sometimes there’s a place for refactoring tests, sometimes I just want my changes’ diff to be as to-the-point as possible.
- If a test uses `assertNotIn` and there is HTML in it – there’s a fair chance the assertion will be more correct as an HTML-aware `assertNotInHTML`. Here again, sometimes there’s a place for making the tests more sound, sometimes it’s better to keep to the existing patterns.

---

<div class="post-metadata">

**Author:** ![Lily-Foote](https://avatars.discourse-cdn.com/v4/letter/l/c37758/32.png) [@Lily-Foote](https://forum.djangoproject.com/u/Lily-Foote)\
**Post date:** [June 19, 2023, 11:24am UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711/3 "2023-06-19T11:24:49Z")

</div>

I’m in favour of adding this. It seems like a straightforward gap in Django to me. If we think there’s a risk of misuse, we should add a warning in the documentation. I think being able to use it for scenarios like moving from `assertNotContains` outweighs the downsides.

---

<div class="post-metadata">

**Author:** ![nessita](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nessita/32/12194_2.png) [@nessita](https://forum.djangoproject.com/u/nessita)\
**Post date:** [June 19, 2023, 7:52pm UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711/4 "2023-06-19T19:52:44Z")

</div>

As mentioned in the ticket, I’m +1 to have `assertNotInHTML` as a counterpart of `assertNotContains`. I have use these `assertNot*` in the past when, for example, a view returned a filtered list of items, and the test needs to ensure that the proper filtering is done.

I’m aware there are other options (arguably more robust) to test this kind of logic (like using a proper `data-test` attribute for each item and grabbing all occurrences with PyQuery to assert over the final list), but I still find these assert helpers quite useful for creating simpler and more straightforward tests.

---

<div class="post-metadata">

**Author:** ![strokirk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/strokirk/32/3414_2.png) [@strokirk](https://forum.djangoproject.com/u/strokirk)\
**Post date:** [June 19, 2023, 9:54pm UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711/5 "2023-06-19T21:54:53Z")

</div>

Makes a lot of sense to have this test method for symmetry. Just because not everyone uses it is not a great reason to exclude it. Refactoring tests is a great example, or as a precondition to a later assertInHtml

---

<div class="post-metadata">

**Author:** ![carltongibson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/carltongibson/32/267_2.png) [@carltongibson](https://forum.djangoproject.com/u/carltongibson)\
**Post date:** [June 20, 2023, 6:09pm UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711/6 "2023-06-20T18:09:28Z")

</div>

I wanted this not the other day…

I was checking that a correct template partial was rendered, rather than the whole template, and wanted to check that a header from another part of the page wasn’t there. I ended up using `assertNotIn` on the exact string, which was _OK_…

I should have used `assertNotContains`, but at that moment I’d forgotten it. 🤦‍♀️

> If I have to switch a test from `assertNotContains(…, html=True)` to something that’s not a response…

I think that’s a good point.

I’d probably lean towards a +1 myself.

---

<div class="post-metadata">

**Author:** ![felixxm](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/felixxm/32/2479_2.png) [@felixxm](https://forum.djangoproject.com/u/felixxm)\
**Post date:** [June 20, 2023, 6:50pm UTC](https://forum.djangoproject.com/t/feature-request-new-assertion-assertnotinhtml-like-assertcontains-has-assertnotcontains/21711/7 "2023-06-20T18:50:05Z")

</div>

I’ve accepted the ticket based on this discussion.
