diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index aea13b94d7..9c78e758f0 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -445,6 +445,10 @@ class InlineAdminFormSet:
     def is_bound(self):
         return self.formset.is_bound
 
+    @property
+    def total_form_count(self):
+        return self.formset.total_form_count
+
     @property
     def media(self):
         media = self.opts.media + self.formset.media
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 0d24bf0d40..a29b77c277 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -686,13 +686,21 @@ class SimpleTestCase(unittest.TestCase):
         for i, context in enumerate(contexts):
             if formset not in context or not hasattr(context[formset], "forms"):
                 continue
+            formset_repr = repr(context[formset])
             if not context[formset].is_bound:
-                formset_repr = repr(context[formset])
                 self.fail(
                     f"{msg_prefix}The formset {formset_repr} is not bound, it will "
                     f"never have any errors."
                 )
             found_formset = True
+            if form_index is not None:
+                form_count = context[formset].total_form_count()
+                if form_index >= form_count:
+                    form_or_forms = "forms" if form_count > 1 else "form"
+                    self.fail(
+                        f"{msg_prefix}The formset {formset_repr} only has "
+                        f"{form_count} {form_or_forms}."
+                    )
             for err in errors:
                 if field is not None:
                     if field in context[formset].forms[form_index].errors:
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index bfc9633122..038e79732b 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -1629,6 +1629,33 @@ class AssertFormsetErrorTests(SimpleTestCase):
         )
         self.assertFormsetError(response, "formset", None, None, "error")
 
+    def test_form_index_too_big(self):
+        msg = (
+            "The formset <TestFormset: bound=True valid=False total_forms=1> only has "
+            "1 form."
+        )
+        response = mock.Mock(context=[{}, {"formset": TestFormset.invalid()}])
+        with self.assertRaisesMessage(AssertionError, msg):
+            self.assertFormsetError(response, "formset", 2, "field", "error")
+
+    def test_form_index_too_big_plural(self):
+        formset = TestFormset(
+            {
+                "form-TOTAL_FORMS": "2",
+                "form-INITIAL_FORMS": "0",
+                "form-0-field": "valid",
+                "form-1-field": "valid",
+            }
+        )
+        formset.full_clean()
+        msg = (
+            "The formset <TestFormset: bound=True valid=True total_forms=2> only has 2 "
+            "forms."
+        )
+        response = mock.Mock(context=[{}, {"formset": formset}])
+        with self.assertRaisesMessage(AssertionError, msg):
+            self.assertFormsetError(response, "formset", 2, "field", "error")
+
     def test_formset_named_form(self):
         formset = TestFormset.invalid()
         # The mocked context emulates the template-based rendering of the