diff --git a/docs/changelog.rst b/docs/changelog.rst index e1c8a982..84b1fb90 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,12 @@ Compatibility * Official Django 6.1 support. +Improvements +^^^^^^^^^^^^ + +* Added the ``django_asserts_max_diff`` configuration option to set the + maximum diff length emitted by ``pytest_django.asserts`` (`#1155 `__). + v4.14.0 (2026-08-10) -------------------- diff --git a/docs/configuring_django.rst b/docs/configuring_django.rst index 75f247e2..4c508a5b 100644 --- a/docs/configuring_django.rst +++ b/docs/configuring_django.rst @@ -59,6 +59,24 @@ The order of precedence is, from highest to lowest: If you want to use the highest precedence in the configuration file, you can use ``addopts = --ds=yourtestsettings``. +Configuring Django assertion diffs +---------------------------------- + +The assertion helpers in ``pytest_django.asserts`` use Django's +``TestCase`` assertions. To change the maximum amount of assertion diff shown, +set ``django_asserts_max_diff`` in your pytest configuration. The default is +``640``, matching ``unittest.TestCase.maxDiff``. Set it to ``None`` to show the +complete diff:: + + [pytest] + django_asserts_max_diff = None + +In ``pyproject.toml``, use a string because TOML does not have a ``None`` +literal:: + + [tool.pytest.ini_options] + django_asserts_max_diff = "None" + Using django-configurations --------------------------- diff --git a/pytest_django/asserts.py b/pytest_django/asserts.py index 47d636d5..d9130354 100644 --- a/pytest_django/asserts.py +++ b/pytest_django/asserts.py @@ -16,7 +16,11 @@ class MessagesTestCase(MessagesTestMixin, TestCase): pass -test_case = MessagesTestCase("run") +test_case: Any = MessagesTestCase("run") + + +def _set_max_diff(max_diff: int | None) -> None: + test_case.maxDiff = max_diff def _wrapper(name: str) -> Callable[..., Any]: diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index aa45ff38..f405988d 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -142,6 +142,11 @@ def pytest_addoption(parser: pytest.Parser) -> None: "How to set the Django DEBUG setting (default `False`). Use `keep` to not override.", default="False", ) + parser.addini( + "django_asserts_max_diff", + "Maximum diff length for pytest_django.asserts (use `None` for no limit).", + default="640", + ) group.addoption( "--fail-on-template-vars", action="store_true", @@ -410,6 +415,22 @@ def pytest_configure(config: pytest.Config) -> None: # it's fully initialized here. _setup_django(config) + if "django" not in sys.modules: + return + + from pytest_django.asserts import _set_max_diff + + max_diff = config.getini("django_asserts_max_diff") + if max_diff.lower() == "none": + _set_max_diff(None) + else: + try: + _set_max_diff(int(max_diff)) + except ValueError as error: + raise pytest.UsageError( + "django_asserts_max_diff must be an integer or None" + ) from error + @pytest.hookimpl() def pytest_report_header(config: pytest.Config) -> list[str] | None: diff --git a/tests/test_asserts.py b/tests/test_asserts.py index 7dbbf710..c59578a6 100644 --- a/tests/test_asserts.py +++ b/tests/test_asserts.py @@ -69,3 +69,51 @@ def test_sanity() -> None: pass assert assertContains.__doc__ + + +def test_django_asserts_max_diff_can_be_unlimited( + django_pytester: pytest.Pytester, +) -> None: + django_pytester.makeini( + """ + [pytest] + django_asserts_max_diff = None + """ + ) + django_pytester.makepyfile( + """ + from pytest_django.asserts import assertXMLEqual + + + def test_assert_xml_equal_uses_the_configured_max_diff(): + expected = "" + "".join(f"{i}" for i in range(100)) + "" + actual = "" + "".join(f"{i + 1}" for i in range(100)) + "" + + try: + assertXMLEqual(expected, actual) + except AssertionError as error: + assert "Set self.maxDiff to None" not in str(error) + else: + raise AssertionError("assertXMLEqual unexpectedly passed") + """ + ) + + result = django_pytester.runpytest_subprocess() + + result.assert_outcomes(passed=1) + + +def test_django_asserts_max_diff_requires_an_integer_or_none( + django_pytester: pytest.Pytester, +) -> None: + django_pytester.makeini( + """ + [pytest] + django_asserts_max_diff = unlimited + """ + ) + + result = django_pytester.runpytest_subprocess() + + assert result.ret == pytest.ExitCode.USAGE_ERROR + result.stderr.fnmatch_lines(["*django_asserts_max_diff must be an integer or None*"]) diff --git a/tests/test_without_django_loaded.py b/tests/test_without_django_loaded.py index cd376fa6..7e93ca45 100644 --- a/tests/test_without_django_loaded.py +++ b/tests/test_without_django_loaded.py @@ -26,6 +26,30 @@ def test_cfg(pytestconfig): assert r.ret == 0 +def test_django_asserts_max_diff_does_not_import_django( + pytester: pytest.Pytester, +) -> None: + pytester.makeini( + """ + [pytest] + django_asserts_max_diff = None + """ + ) + pytester.makepyfile( + """ + import sys + + + def test_django_is_not_imported(): + assert "django" not in sys.modules + """ + ) + + result = pytester.runpytest_subprocess() + + result.assert_outcomes(passed=1) + + def test_database(pytester: pytest.Pytester) -> None: pytester.makepyfile( """