pax_global_header00006660000000000000000000000064141536673240014525gustar00rootroot0000000000000052 comment=27d65607d82f6915bbc56f73779eab013f596708 pytest-django-4.5.2/000077500000000000000000000000001415366732400143255ustar00rootroot00000000000000pytest-django-4.5.2/.coveragerc000066400000000000000000000003221415366732400164430ustar00rootroot00000000000000[run] parallel = 1 source = ${PYTESTDJANGO_COVERAGE_SRC}. branch = 1 [report] include = pytest_django/*,pytest_django_test/*,tests/* skip_covered = 1 exclude_lines = pragma: no cover if TYPE_CHECKING: pytest-django-4.5.2/.github/000077500000000000000000000000001415366732400156655ustar00rootroot00000000000000pytest-django-4.5.2/.github/workflows/000077500000000000000000000000001415366732400177225ustar00rootroot00000000000000pytest-django-4.5.2/.github/workflows/main.yml000066400000000000000000000101331415366732400213670ustar00rootroot00000000000000name: main on: push: branches: - master tags: - "*" pull_request: branches: - master env: PYTEST_ADDOPTS: "--color=yes" # Set permissions at the job level. permissions: {} jobs: test: runs-on: ubuntu-20.04 continue-on-error: ${{ matrix.allow_failure }} timeout-minutes: 15 permissions: contents: read steps: - uses: actions/checkout@v2 with: persist-credentials: false - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - name: Setup mysql if: contains(matrix.name, 'mysql') run: | sudo systemctl start mysql.service echo "TEST_DB_USER=root" >> $GITHUB_ENV echo "TEST_DB_PASSWORD=root" >> $GITHUB_ENV - name: Setup postgresql if: contains(matrix.name, 'postgres') run: | sudo systemctl start postgresql.service sudo -u postgres createuser --createdb $USER - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox==3.24.4 - name: Run tox run: tox -e ${{ matrix.name }} - name: Report coverage if: contains(matrix.name, 'coverage') uses: codecov/codecov-action@v2 with: fail_ci_if_error: true files: ./coverage.xml strategy: fail-fast: false matrix: include: - name: linting,docs python: 3.8 allow_failure: false - name: py310-dj40-postgres-xdist-coverage python: '3.10' allow_failure: false - name: py310-dj32-postgres-xdist-coverage python: '3.10' allow_failure: false - name: py39-dj32-postgres-xdist-coverage python: 3.9 allow_failure: false - name: py39-dj31-postgres-xdist-coverage python: 3.9 allow_failure: false - name: py39-dj40-mysql_innodb-coverage python: 3.9 allow_failure: false - name: py37-dj31-mysql_innodb-coverage python: 3.7 allow_failure: false - name: py36-dj22-sqlite-xdist-coverage python: 3.6 allow_failure: false - name: py37-dj22-sqlite-xdist-coverage python: 3.7 allow_failure: false - name: py38-dj32-sqlite-xdist-coverage python: 3.8 allow_failure: false - name: py38-dj31-sqlite-xdist-coverage python: 3.8 allow_failure: false - name: py38-dj40-sqlite-xdist-coverage python: 3.8 allow_failure: false - name: py39-djmain-sqlite-coverage python: 3.9 allow_failure: true # Explicitly test (older) pytest 5.4. - name: py35-dj22-postgres-pytest54-coverage python: 3.5 allow_failure: false - name: py35-dj22-sqlite_file-coverage python: 3.5 allow_failure: false - name: py36-dj31-mysql_myisam-coverage python: 3.6 allow_failure: false - name: py36-dj32-mysql_myisam-coverage python: 3.6 allow_failure: false # pypy3: not included with coverage reports (much slower then). - name: pypy3-dj22-postgres python: pypy3 allow_failure: false deploy: if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest-django' runs-on: ubuntu-20.04 timeout-minutes: 15 permissions: contents: read steps: - uses: actions/checkout@v2 with: fetch-depth: 0 persist-credentials: false - uses: actions/setup-python@v2 with: python-version: "3.8" - name: Install dependencies run: | python -m pip install --upgrade pip pip install --upgrade build - name: Build package run: python -m build - name: Publish package uses: pypa/gh-action-pypi-publish@v1.4.1 with: user: __token__ password: ${{ secrets.pypi_token }} pytest-django-4.5.2/.gitignore000066400000000000000000000003241415366732400163140ustar00rootroot00000000000000*.pyc build/ /dist/ *.egg-info _build .tox .DS_Store *~ .env /.coverage.* /.coverage /coverage.xml /htmlcov/ .cache .pytest_cache/ .Python .eggs *.egg # autogenerated by setuptools-scm /pytest_django/_version.py pytest-django-4.5.2/.readthedocs.yml000066400000000000000000000003011415366732400174050ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py python: version: 3 install: - method: pip path: . extra_requirements: - docs formats: - epub - pdf pytest-django-4.5.2/AUTHORS000066400000000000000000000011551415366732400153770ustar00rootroot00000000000000Ben Firshman created the original version of pytest-django. This project is currently maintained by Ran Benita . Previous maintainers are: Andreas Pelme Daniel Hahler These people have provided bug fixes, new features, improved the documentation or just made pytest-django more awesome: Ruben Bakker Ralf Schmitt Rob Berry Floris Bruynooghe Rafal Stozek Donald Stufft Nicolas Delaby Hasan Ramezani Michael Howitz pytest-django-4.5.2/LICENSE000066400000000000000000000062231415366732400153350ustar00rootroot00000000000000pytest-django is released under the BSD (3-clause) license ---------------------------------------------------------- Copyright (c) 2015-2018, pytest-django authors (see AUTHORS file) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This version of pytest-django is a fork of pytest_django created by Ben Firshman. --------------------------------------------------------------------------------- Copyright (c) 2009, Ben Firshman All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pytest-django-4.5.2/MANIFEST.in000066400000000000000000000006401415366732400160630ustar00rootroot00000000000000include AUTHORS include README.rst include LICENSE # Include tests for downstream testing (https://github.com/pytest-dev/pytest-django/issues/290). recursive-include tests *.py *.txt recursive-include pytest_django_test *.json *.py *.txt recursive-exclude .git * recursive-exclude .tox * recursive-exclude bin * recursive-exclude include * recursive-exclude lib * recursive-exclude share * recursive-exclude src * pytest-django-4.5.2/Makefile000066400000000000000000000010031415366732400157570ustar00rootroot00000000000000.PHONY: docs test clean isort VENV:=build/venv export DJANGO_SETTINGS_MODULE?=pytest_django_test.settings_sqlite_file test: $(VENV)/bin/pytest $(VENV)/bin/pytest $(VENV)/bin/python $(VENV)/bin/pip: virtualenv $(VENV) $(VENV)/bin/pytest: $(VENV)/bin/python requirements.txt $(VENV)/bin/pip install -Ur requirements.txt touch $@ docs: tox -e docs # See setup.cfg for configuration. isort: isort pytest_django pytest_django_test tests clean: rm -rf bin include/ lib/ man/ pytest_django.egg-info/ build/ pytest-django-4.5.2/README.rst000066400000000000000000000056651415366732400160300ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/pytest-django.svg?style=flat :alt: PyPI Version :target: https://pypi.python.org/pypi/pytest-django .. image:: https://img.shields.io/pypi/pyversions/pytest-django.svg :alt: Supported Python versions :target: https://pypi.python.org/pypi/pytest-django .. image:: https://github.com/pytest-dev/pytest-django/workflows/main/badge.svg :alt: Build Status :target: https://github.com/pytest-dev/pytest-django/actions .. image:: https://img.shields.io/pypi/djversions/pytest-django.svg :alt: Supported Django versions :target: https://pypi.org/project/pytest-django/ .. image:: https://img.shields.io/codecov/c/github/pytest-dev/pytest-django.svg?style=flat :alt: Coverage :target: https://codecov.io/gh/pytest-dev/pytest-django Welcome to pytest-django! ========================= pytest-django allows you to test your Django project/applications with the `pytest testing tool `_. * `Quick start / tutorial `_ * `Changelog `_ * Full documentation: https://pytest-django.readthedocs.io/en/latest/ * `Contribution docs `_ * Version compatibility: * Django: 2.2, 3.1, 3.2, 4.0 and latest main branch (compatible at the time of each release) * Python: CPython>=3.5 or PyPy 3 * pytest: >=5.4 For compatibility with older versions, use the pytest-django 3.*.* series. * Licence: BSD * Project maintainers: Andreas Pelme, Floris Bruynooghe and Daniel Hahler * `All contributors `_ * GitHub repository: https://github.com/pytest-dev/pytest-django * `Issue tracker `_ * `Python Package Index (PyPI) `_ Install pytest-django --------------------- :: pip install pytest-django Why would I use this instead of Django's `manage.py test` command? ------------------------------------------------------------------ Running your test suite with pytest-django allows you to tap into the features that are already present in pytest. Here are some advantages: * `Manage test dependencies with pytest fixtures. `_ * Less boilerplate tests: no need to import unittest, create a subclass with methods. Write tests as regular functions. * Database re-use: no need to re-create the test database for every test run. * Run tests in multiple processes for increased speed (with the pytest-xdist plugin). * Make use of other `pytest plugins `_. * Works with both worlds: Existing unittest-style TestCase's still work without any modifications. See the `pytest documentation `_ for more information on pytest itself. pytest-django-4.5.2/codecov.yml000066400000000000000000000002051415366732400164670ustar00rootroot00000000000000# reference: https://docs.codecov.io/docs/codecovyml-reference coverage: status: patch: true project: false comment: false pytest-django-4.5.2/docs/000077500000000000000000000000001415366732400152555ustar00rootroot00000000000000pytest-django-4.5.2/docs/Makefile000066400000000000000000000127701415366732400167240ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = ifndef SPHINXBUILD SPHINXBUILD = ../bin/sphinx-build endif PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytest-django.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest-django.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pytest-django" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest-django" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pytest-django-4.5.2/docs/_ext/000077500000000000000000000000001415366732400162145ustar00rootroot00000000000000pytest-django-4.5.2/docs/_ext/pytestdocs.py000066400000000000000000000002311415366732400207630ustar00rootroot00000000000000def setup(app): app.add_crossref_type( directivename="fixture", rolename="fixture", indextemplate="pair: %s; fixture", ) pytest-django-4.5.2/docs/changelog.rst000066400000000000000000000562741415366732400177540ustar00rootroot00000000000000Changelog ========= v4.5.2 (2021-12-07) ------------------- Bugfixes ^^^^^^^^ * Fix regression in v4.5.0 - ``pytest.mark.django_db(reset_sequence=True)`` now implies ``transaction=True`` again. v4.5.1 (2021-12-02) ------------------- Bugfixes ^^^^^^^^ * Fix regression in v4.5.0 - database tests inside (non-unittest) classes were not ordered correctly to run before non-database tests, same for transactional tests before non-transactional tests. v4.5.0 (2021-12-01) ------------------- Improvements ^^^^^^^^^^^^ * Add support for :ref:`rollback emulation/serialized rollback `. The :func:`pytest.mark.django_db` marker has a new ``serialized_rollback`` option, and a :fixture:`django_db_serialized_rollback` fixture is added. * Official Python 3.10 support. * Official Django 4.0 support (tested against 4.0rc1 at the time of release). * Drop official Django 3.0 support. Django 2.2 is still supported, and 3.0 will likely keep working until 2.2 is dropped, but it's not tested. * Added pyproject.toml file. * Skip Django's `setUpTestData` mechanism in pytest-django tests. It is not used for those, and interferes with some planned features. Note that this does not affect ``setUpTestData`` in unittest tests (test classes which inherit from Django's `TestCase`). Bugfixes ^^^^^^^^ * Fix :fixture:`live_server` when using an in-memory SQLite database. * Fix typing of ``assertTemplateUsed`` and ``assertTemplateNotUsed``. v4.4.0 (2021-06-06) ------------------- Improvements ^^^^^^^^^^^^ * Add a fixture :fixture:`django_capture_on_commit_callbacks` to capture :func:`transaction.on_commit() ` callbacks in tests. v4.3.0 (2021-05-15) ------------------- Improvements ^^^^^^^^^^^^ * Add experimental :ref:`multiple databases ` (multi db) support. * Add type annotations. If you previously excluded ``pytest_django`` from your type-checker, you can remove the exclusion. * Documentation improvements. v4.2.0 (2021-04-10) ------------------- Improvements ^^^^^^^^^^^^ * Official Django 3.2 support. * Documentation improvements. Bugfixes ^^^^^^^^ * Disable atomic durability check on non-transactional tests (#910). v4.1.0 (2020-10-22) ------------------- Improvements ^^^^^^^^^^^^ * Add the :fixture:`async_client` and :fixture:`async_rf` fixtures (#864). * Add :ref:`django_debug_mode ` to configure how ``DEBUG`` is set in tests (#228). * Documentation improvements. Bugfixes ^^^^^^^^ * Make :fixture:`admin_user` work for custom user models without an ``email`` field. v4.0.0 (2020-10-16) ------------------- Compatibility ^^^^^^^^^^^^^ This release contains no breaking changes, except dropping compatibility with some older/unsupported versions. * Drop support for Python versions before 3.5 (#868). Previously 2.7 and 3.4 were supported. Running ``pip install pytest-django`` on Python 2.7 or 3.4 would continue to install the compatible 3.x series. * Drop support for Django versions before 2.2 (#868). Previously Django>=1.8 was supported. * Drop support for pytest versions before 5.4 (#868). Previously pytest>=3.6 was supported. Improvements ^^^^^^^^^^^^ * Officially support Python 3.9. * Add ``pytest_django.__version__`` (#880). * Minor documentation improvements (#882). Bugfixes ^^^^^^^^ * Make the ``admin_user`` and ``admin_client`` fixtures compatible with custom user models which don't have a ``username`` field (#457). * Change the ``admin_user`` fixture to use ``get_by_natural_key()`` to get the user instead of directly using ``USERNAME_FIELD``, in case it is overridden, and to match Django (#879). Misc ^^^^ * Fix pytest-django's own tests failing due to some deprecation warnings (#875). v3.10.0 (2020-08-25) -------------------- Improvements ^^^^^^^^^^^^ * Officially support Django 3.1 * Preliminary support for upcoming Django 3.2 * Support for pytest-xdist 2.0 Misc ^^^^ * Fix running pytest-django's own tests against pytest 6.0 (#855) v3.9.0 (2020-03-31) ------------------- Improvements ^^^^^^^^^^^^ * Improve test ordering with Django test classes (#830) * Remove import of pkg_resources for parsing pytest version (performance) (#826) Bugfixes ^^^^^^^^ * Work around unittest issue with pytest 5.4.{0,1} (#825) * Don't break --failed-first when re-ordering tests (#819, #820) * pytest_addoption: use `group.addoption` (#833) Misc ^^^^ * Remove Django version from --nomigrations heading (#822) * docs: changelog: prefix headers with v for permalink anchors * changelog: add custom/fixed anchor for last version * setup.py: add Changelog to project_urls v3.8.0 (2020-01-14) -------------------- Improvements ^^^^^^^^^^^^ * Make Django's assertion helpers available in pytest_django.asserts (#709). * Report django-configurations setting (#791) v3.7.0 (2019-11-09) ------------------- Bugfixes ^^^^^^^^ * Monkeypatch pytest to not use ``TestCase.debug`` with unittests, instead of patching it into Django (#782). * Work around pytest crashing due to ``pytest.fail`` being used from within the DB blocker, and pytest trying to display an object representation involving DB access (#781). pytest-django uses a ``RuntimeError`` now instead. v3.6.0 (2019-10-17) ------------------- Features ^^^^^^^^ * Rename test databases when running parallel Tox (#678, #680) Bugfixes ^^^^^^^^ * Django unittests: restore "debug" function (#769, #771) Misc ^^^^ * Improve/harden internal tests / infrastructure. v3.5.1 (2019-06-29) ------------------- Bugfixes ^^^^^^^^ * Fix compatibility with pytest 5.x (#751) v3.5.0 (2019-06-03) ------------------- Features ^^^^^^^^ * Run tests in the same order as Django (#223) * Use verbosity=0 with disabled migrations (#729, #730) Bugfixes ^^^^^^^^ * django_db_setup: warn instead of crash with teardown errors (#726) Misc ^^^^ * tests: fix test_sqlite_database_renamed (#739, #741) * tests/conftest.py: move import of db_helpers (#737) * Cleanup/improve coverage, mainly with tests (#706) * Slightly revisit unittest handling (#740) v3.4.8 (2019-02-26) ------------------- Bugfixes ^^^^^^^^ * Fix DB renaming fixture for Multi-DB environment with SQLite (#679) v3.4.7 (2019-02-03) ------------------- Bugfixes ^^^^^^^^ * Fix disabling/handling of unittest methods with pytest 4.2+ (#700) v3.4.6 (2019-02-01) ------------------- Bugfixes ^^^^^^^^ * django_find_project: add cwd as fallback always (#690) Misc ^^^^ * Enable tests for Django 2.2 and add classifier (#693) * Disallow pytest 4.2.0 in ``install_requires`` (#697) v3.4.5 (2019-01-07) ------------------- Bugfixes ^^^^^^^^ * Use ``request.config`` instead of ``pytest.config`` (#677) * :fixture:`admin_user`: handle "email" username_field (#676) Misc ^^^^ * Minor doc fixes (#674) * tests: fix for pytest 4 (#675) v3.4.4 (2018-11-13) ------------------- Bugfixes ^^^^^^^^ * Refine the django.conf module check to see if the settings really are configured (#668). * Avoid crash after OSError during Django path detection (#664). Features ^^^^^^^^ * Add parameter info to fixture assert_num_queries to display additional message on failure (#663). Docs ^^^^ * Improve doc for django_assert_num_queries/django_assert_max_num_queries. * Add warning about sqlite specific snippet + fix typos (#666). Misc ^^^^ * MANIFEST.in: include tests for downstream distros (#653). * Ensure that the LICENSE file is included in wheels (#665). * Run black on source. v3.4.3 (2018-09-16) ------------------- Bugfixes ^^^^^^^^ * Fix OSError with arguments containing ``::`` on Windows (#641). v3.4.2 (2018-08-20) ------------------- Bugfixes ^^^^^^^^ * Changed dependency for pathlib to pathlib2 (#636). * Fixed code for inserting the project to sys.path with pathlib to use an absolute path, regression in 3.4.0 (#637, #638). v3.4.0 (2018-08-16) ------------------- Features ^^^^^^^^ * Added new fixture :fixture:`django_assert_max_num_queries` (#547). * Added support for ``connection`` and returning the wrapped context manager with :fixture:`django_assert_num_queries` (#547). * Added support for resetting sequences via :fixture:`django_db_reset_sequences` (#619). Bugfixes ^^^^^^^^ * Made sure to not call django.setup() multiple times (#629, #531). Compatibility ^^^^^^^^^^^^^ * Removed py dependency, use pathlib instead (#631). v3.3.3 (2018-07-26) ------------------- Bug fixes ^^^^^^^^^ * Fixed registration of :py:func:`~pytest.mark.ignore_template_errors` marker, which is required with ``pytest --strict`` (#609). * Fixed another regression with unittest (#624, #625). Docs ^^^^ * Use sphinx_rtf_theme (#621). * Minor fixes. v3.3.2 (2018-06-21) ------------------- Bug fixes ^^^^^^^^^ * Fixed test for classmethod with Django TestCases again (#618, introduced in #598 (3.3.0)). Compatibility ^^^^^^^^^^^^^ * Support Django 2.1 (no changes necessary) (#614). v3.3.0 (2018-06-15) ------------------- Features ^^^^^^^^ * Added new fixtures ``django_mail_dnsname`` and ``django_mail_patch_dns``, used by ``mailoutbox`` to monkeypatch the ``DNS_NAME`` used in :py:mod:`django.core.mail` to improve performance and reproducibility. Bug fixes ^^^^^^^^^ * Fixed test for classmethod with Django TestCases (#597, #598). * Fixed RemovedInPytest4Warning: MarkInfo objects are deprecated (#596, #603) * Fixed scope of overridden settings with live_server fixture: previously they were visible to following tests (#612). Compatibility ^^^^^^^^^^^^^ * The required `pytest` version changed from >=2.9 to >=3.6. v3.2.1 ------ * Fixed automatic deployment to PyPI. v3.2.0 ------ Features ^^^^^^^^ * Added new fixture `django_assert_num_queries` for testing the number of database queries (#387). * `--fail-on-template-vars` has been improved and should now return full/absolute path (#470). * Support for setting the live server port (#500). * unittest: help with setUpClass not being a classmethod (#544). Bug fixes ^^^^^^^^^ * Fix --reuse-db and --create-db not working together (#411). * Numerous fixes in the documentation. These should not go unnoticed 🌟 Compatibility ^^^^^^^^^^^^^ * Support for Django 2.0 has been added. * Support for Django before 1.8 has been dropped. v3.1.2 ------ Bug fixes ^^^^^^^^^ * Auto clearing of ``mail.outbox`` has been re-introduced to not break functionality in 3.x.x release. This means that Compatibility issues mentioned in the 3.1.0 release are no longer present. Related issue: `pytest-django issue `__ v3.1.1 ------ Bug fixes ^^^^^^^^^ * Workaround `--pdb` interaction with Django TestCase. The issue is caused by Django TestCase not implementing TestCase.debug() properly but was brought to attention with recent changes in pytest 3.0.2. Related issues: `pytest issue `__, `Django issue `__ v3.1.0 ------ Features ^^^^^^^^ * Added new function scoped fixture ``mailoutbox`` that gives access to djangos ``mail.outbox``. The will clean/empty the ``mail.outbox`` to assure that no old mails are still in the outbox. * If ``django.contrib.sites`` is in your INSTALLED_APPS, Site cache will be cleared for each test to avoid hitting the cache and cause wrong Site object to be returned by ``Site.objects.get_current()``. Compatibility ^^^^^^^^^^^^^ * IMPORTANT: the internal autouse fixture _django_clear_outbox has been removed. If you have relied on this to get an empty outbox for your test, you should change tests to use the ``mailoutbox`` fixture instead. See documentation of ``mailoutbox`` fixture for usage. If you try to access mail.outbox directly, AssertionError will be raised. If you previously relied on the old behaviour and do not want to change your tests, put this in your project conftest.py:: @pytest.fixture(autouse=True) def clear_outbox(): from django.core import mail mail.outbox = [] v3.0.0 ------ Bug fixes ^^^^^^^^^ * Fix error when Django happens to be imported before pytest-django runs. Thanks to Will Harris for `the bug report `__. Features ^^^^^^^^ * Added a new option ``--migrations`` to negate a default usage of ``--nomigrations``. * The previously internal pytest-django fixture that handles database creation and setup has been refactored, refined and made a public API. This opens up more flexibility and advanced use cases to configure the test database in new ways. See :ref:`advanced-database-configuration` for more information on the new fixtures and example use cases. Compatibility ^^^^^^^^^^^^^ * Official for the pytest 3.0.0 (2.9.2 release should work too, though). The documentation is updated to mention ``pytest`` instead of ``py.test``. * Django versions 1.4, 1.5 and 1.6 is no longer supported. The supported versions are now 1.7 and forward. Django master is supported as of 2016-08-21. * pytest-django no longer supports Python 2.6. * Specifying the ``DJANGO_TEST_LIVE_SERVER_ADDRESS`` environment variable is no longer supported. Use ``DJANGO_LIVE_TEST_SERVER_ADDRESS`` instead. * Ensuring accidental database access is now stricter than before. Previously database access was prevented on the cursor level. To be safer and prevent more cases, it is now prevented at the connection level. If you previously had tests which interacted with the databases without a database cursor, you will need to mark them with the ``pytest.mark.django_db`` marker or request the ``db`` fixture. * The previously undocumented internal fixtures ``_django_db_setup``, ``_django_cursor_wrapper`` have been removed in favour of the new public fixtures. If you previously relied on these internal fixtures, you must update your code. See :ref:`advanced-database-configuration` for more information on the new fixtures and example use cases. v2.9.1 ------ Bug fixes ^^^^^^^^^ * Fix regression introduced in 2.9.0 that caused TestCase subclasses with mixins to cause errors. Thanks MikeVL for `the bug report `__. v2.9.0 ------ v2.9.0 focus on compatibility with Django 1.9 and master as well as pytest 2.8.1 and Python 3.5 Features ^^^^^^^^ * ``--fail-on-template-vars`` - fail tests for invalid variables in templates. Thanks to Johannes Hoppe for idea and implementation. Thanks Daniel Hahler for review and feedback. Bug fixes ^^^^^^^^^ * Ensure urlconf is properly reset when using @pytest.mark.urls. Thanks to Sarah Bird, David Szotten, Daniel Hahler and Yannick PÉROUX for patch and discussions. Fixes `issue #183 `__. * Call ``setUpClass()`` in Django ``TestCase`` properly when test class is inherited multiple places. Thanks to Benedikt Forchhammer for report and initial test case. Fixes `issue #265 `__. Compatibility ^^^^^^^^^^^^^ * Settings defined in ``pytest.ini``/``tox.ini``/``setup.cfg`` used to override ``DJANGO_SETTINGS_MODULE`` defined in the environment. Previously the order was undocumented. Now, instead the settings from the environment will be used instead. If you previously relied on overriding the environment variable, you can instead specify ``addopts = --ds=yourtestsettings`` in the ini-file which will use the test settings. See `PR #199 `__. * Support for Django 1.9. * Support for Django master (to be 1.10) as of 2015-10-06. * Drop support for Django 1.3. While pytest-django supports a wide range of Django versions, extended for Django 1.3 was dropped in february 2013. v2.8.0 ------ Features ^^^^^^^^ * pytest's verbosity is being used for Django's code to setup/teardown the test database (#172). * Added a new option `--nomigrations` to avoid running Django 1.7+ migrations when constructing the test database. Huge thanks to Renan Ivo for complete patch, tests and documentation. Bug fixes ^^^^^^^^^ * Fixed compatibility issues related to Django 1.8's `setUpClass`/`setUpTestData`. Django 1.8 is now a fully supported version. Django master as of 2014-01-18 (the Django 1.9 branch) is also supported. v2.7.0 ------ Features ^^^^^^^^ * New fixtures: ``admin_user``, ``django_user_model`` and ``django_username_field`` (#109). * Automatic discovery of Django projects to make it easier for new users. This change is slightly backward incompatible, if you encounter problems with it, the old behaviour can be restored by adding this to ``pytest.ini``, ``setup.cfg`` or ``tox.ini``: .. code-block:: ini [pytest] django_find_project = false Please see the :ref:`managing_python_path` section for more information. Bugfixes ^^^^^^^^ * Fix interaction between ``db`` and ``transaction_db`` fixtures (#126). * Fix admin client with custom user models (#124). Big thanks to Benjamin Hedrich and Dmitry Dygalo for patch and tests. * Fix usage of South migrations, which were unconditionally disabled previously (#22). * Fixed #119, #134: Call ``django.setup()`` in Django >=1.7 directly after settings is loaded to ensure proper loading of Django applications. Thanks to Ionel Cristian Mărieș, Daniel Hahler, Tymur Maryokhin, Kirill SIbirev, Paul Collins, Aymeric Augustin, Jannis Leidel, Baptiste Mispelon and Anatoly Bubenkoff for report, discussion and feedback. * `The `live_server`` fixture can now serve static files also for Django>=1.7 if the ``django.contrib.staticfiles`` app is installed. (#140). * ``DJANGO_LIVE_TEST_SERVER_ADDRESS`` environment variable is read instead of ``DJANGO_TEST_LIVE_SERVER_ADDRESS``. (#140) v2.6.2 ------ * Fixed a bug that caused doctests to runs. Thanks to @jjmurre for the patch * Fixed issue #88 - make sure to use SQLite in memory database when running with pytest-xdist. v2.6.1 ------ This is a bugfix/support release with no new features: * Added support for Django 1.7 beta and Django master as of 2014-04-16. pytest-django is now automatically tested against the latest git master version of Django. * Support for MySQL with MyISAM tables. Thanks to Zach Kanzler and Julen Ruiz Aizpuru for fixing this. This fixes issue #8 #64. v2.6.0 ------ * Experimental support for Django 1.7 / Django master as of 2014-01-19. pytest-django is now automatically tested against the latest git version of Django. The support is experimental since Django 1.7 is not yet released, but the goal is to always be up to date with the latest Django master v2.5.1 ------ Invalid release accidentally pushed to PyPI (identical to 2.6.1). Should not be used - use 2.6.1 or newer to avoid confusion. v2.5.0 ------ * Python 2.5 compatibility dropped. py.test 2.5 dropped support for Python 2.5, therefore it will be hard to properly support in pytest-django. The same strategy as for pytest itself is used: No code will be changed to prevent Python 2.5 from working, but it will not be actively tested. * pytest-xdist support: it is now possible to run tests in parallel. Just use pytest-xdist as normal (pass -n to py.test). One database will be created for each subprocess so that tests run independent from each other. v2.4.0 ------ * Support for py.test 2.4 pytest_load_initial_conftests. This makes it possible to import Django models in project conftest.py files, since pytest-django will be initialized before the conftest.py is loaded. v2.3.1 ------ * Support for Django 1.5 custom user models, thanks to Leonardo Santagada. v2.3.0 ------ * Support for configuring settings via django-configurations. Big thanks to Donald Stufft for this feature! v2.2.1 ------ * Fixed an issue with the settings fixture when used in combination with django-appconf. It now uses pytest's monkeypatch internally and should be more robust. v2.2.0 ------ * Python 3 support. pytest-django now supports Python 3.2 and 3.3 in addition to 2.5-2.7. Big thanks to Rafal Stozek for making this happen! v2.1.0 ------ * Django 1.5 support. pytest-django is now tested against 1.5 for Python 2.6-2.7. This is the first step towards Python 3 support. v2.0.1 ------ * Fixed #24/#25: Make it possible to configure Django via ``django.conf.settings.configure()``. * Fixed #26: Don't set DEBUG_PROPAGATE_EXCEPTIONS = True for test runs. Django does not change this setting in the default test runner, so pytest-django should not do it either. v2.0.0 ------ This release is *backward incompatible*. The biggest change is the need to add the ``pytest.mark.django_db`` to tests which require database access. Finding such tests is generally very easy: just run your test suite, the tests which need database access will fail. Add ``pytestmark = pytest.mark.django_db`` to the module/class or decorate them with ``@pytest.mark.django_db``. Most of the internals have been rewritten, exploiting py.test's new fixtures API. This release would not be possible without Floris Bruynooghe who did the port to the new fixture API and fixed a number of bugs. The tests for pytest-django itself has been greatly improved, paving the way for easier additions of new and exciting features in the future! * Semantic version numbers will now be used for releases, see https://semver.org/. * Do not allow database access in tests by default. Introduce ``pytest.mark.django_db`` to enable database access. * Large parts re-written using py.test's 2.3 fixtures API (issue #9). - Fixes issue #17: Database changes made in fixtures or funcargs will now be reverted as well. - Fixes issue 21: Database teardown errors are no longer hidden. - Fixes issue 16: Database setup and teardown for non-TestCase classes works correctly. * ``pytest.urls()`` is replaced by the standard marking API and is now used as ``pytest.mark.urls()`` * Make the plugin behave gracefully without DJANGO_SETTINGS_MODULE specified. ``py.test`` will still work and tests needing django features will skip (issue #3). * Allow specifying of ``DJANGO_SETTINGS_MODULE`` on the command line (``--ds=settings``) and py.test ini configuration file as well as the environment variable (issue #3). * Deprecate the ``transaction_test_case`` decorator, this is now integrated with the ``django_db`` mark. v1.4 ---- * Removed undocumented pytest.load_fixture: If you need this feature, just use ``django.management.call_command('loaddata', 'foo.json')`` instead. * Fixed issue with RequestFactory in Django 1.3. * Fixed issue with RequestFactory in Django 1.3. v1.3 ---- * Added ``--reuse-db`` and ``--create-db`` to allow database re-use. Many thanks to `django-nose `__ for code and inspiration for this feature. v1.2.2 ------ * Fixed Django 1.3 compatibility. v1.2.1 ------ * Disable database access and raise errors when using --no-db and accessing the database by accident. v1.2 ---- * Added the ``--no-db`` command line option. v1.1.1 ------ * Flush tables after each test run with transaction_test_case instead of before. v1.1 ---- * The initial release of this fork from `Ben Firshman original project `__ * Added documentation * Uploaded to PyPI for easy installation * Added the ``transaction_test_case`` decorator for tests that needs real transactions * Added initial implementation for live server support via a funcarg (no docs yet, it might change!) pytest-django-4.5.2/docs/conf.py000066400000000000000000000031461415366732400165600ustar00rootroot00000000000000import os import sys import datetime # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'pytestdocs', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'pytest-django' copyright = u'%d, Andreas Pelme and contributors' % datetime.date.today().year exclude_patterns = ['_build'] pygments_style = 'sphinx' html_theme = 'sphinx_rtd_theme' # Output file base name for HTML help builder. htmlhelp_basename = 'pytest-djangodoc' intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'django': ('https://docs.djangoproject.com/en/stable/', 'https://docs.djangoproject.com/en/stable/_objects/'), 'pytest': ('https://docs.pytest.org/en/stable/', None), } def setup(app): # Allow linking to pytest's confvals. app.add_object_type( "confval", "pytest-confval", objname="configuration value", indextemplate="pair: %s; configuration value", ) pytest-django-4.5.2/docs/configuring_django.rst000066400000000000000000000064501415366732400216500ustar00rootroot00000000000000.. _configuring_django_settings: Configuring Django settings =========================== There are a couple of different ways Django settings can be provided for the tests. The environment variable ``DJANGO_SETTINGS_MODULE`` --------------------------------------------------- Running the tests with ``DJANGO_SETTINGS_MODULE`` defined will find the Django settings the same way Django does by default. Example:: $ export DJANGO_SETTINGS_MODULE=test.settings $ pytest or:: $ DJANGO_SETTINGS_MODULE=test.settings pytest Command line option ``--ds=SETTINGS`` ------------------------------------- Example:: $ pytest --ds=test.settings ``pytest.ini`` settings ----------------------- Example contents of pytest.ini:: [pytest] DJANGO_SETTINGS_MODULE = test.settings Order of choosing settings -------------------------- The order of precedence is, from highest to lowest: * The command line option ``--ds`` * The environment variable ``DJANGO_SETTINGS_MODULE`` * The ``DJANGO_SETTINGS_MODULE`` option in the configuration file - ``pytest.ini``, or other file that Pytest finds such as ``tox.ini`` If you want to use the highest precedence in the configuration file, you can use ``addopts = --ds=yourtestsettings``. Using django-configurations --------------------------- There is support for using `django-configurations `_. To do so configure the settings class using an environment variable, the ``--dc`` flag, or ``pytest.ini`` option ``DJANGO_CONFIGURATION``. Environment Variable:: $ export DJANGO_CONFIGURATION=MySettings $ pytest Command Line Option:: $ pytest --dc=MySettings INI File Contents:: [pytest] DJANGO_CONFIGURATION=MySettings Using ``django.conf.settings.configure()`` ------------------------------------------ In case there is no ``DJANGO_SETTINGS_MODULE``, the ``settings`` object can be created by calling ``django.conf.settings.configure()``. This can be done from your project's ``conftest.py`` file:: from django.conf import settings def pytest_configure(): settings.configure(DATABASES=...) Overriding individual settings ------------------------------ Settings can be overridden by using the :fixture:`settings` fixture:: @pytest.fixture(autouse=True) def use_dummy_cache_backend(settings): settings.CACHES = { "default": { "BACKEND": "django.core.cache.backends.dummy.DummyCache", } } Here `autouse=True` is used, meaning the fixture is automatically applied to all tests, but it can also be requested individually per-test. Changing your app before Django gets set up ------------------------------------------- pytest-django calls :py:func:`django.setup` automatically. If you want to do anything before this, you have to create a pytest plugin and use the :py:func:`~_pytest.hookspec.pytest_load_initial_conftests` hook, with ``tryfirst=True``, so that it gets run before the hook in pytest-django itself:: @pytest.hookimpl(tryfirst=True) def pytest_load_initial_conftests(early_config, parser, args): import project.app.signals def noop(*args, **kwargs): pass project.app.signals.something = noop This plugin can then be used e.g. via ``-p`` in :pytest-confval:`addopts`. pytest-django-4.5.2/docs/contributing.rst000066400000000000000000000216201415366732400205170ustar00rootroot00000000000000############################# Contributing to pytest-django ############################# Like every open-source project, pytest-django is always looking for motivated individuals to contribute to its source code. However, to ensure the highest code quality and keep the repository nice and tidy, everybody has to follow a few rules (nothing major, I promise :) ) ********* Community ********* The fastest way to get feedback on contributions/bugs is usually to open an issue in the `issue tracker`_. Discussions also happen via IRC in #pytest `on irc.libera.chat `_ (join using an IRC client, `via webchat `_, or `via Matrix `_). You may also be interested in following `@andreaspelme`_ on Twitter. ************* In a nutshell ************* Here's what the contribution process looks like, in a bullet-points fashion: #. pytest-django is hosted on `GitHub`_, at https://github.com/pytest-dev/pytest-django #. The best method to contribute back is to create an account there and fork the project. You can use this fork as if it was your own project, and should push your changes to it. #. When you feel your code is good enough for inclusion, "send us a `pull request`_", by using the nice GitHub web interface. ***************** Contributing Code ***************** Getting the source code ======================= - Code will be reviewed and tested by at least one core developer, preferably by several. Other community members are welcome to give feedback. - Code *must* be tested. Your pull request should include unit-tests (that cover the piece of code you're submitting, obviously). - Documentation should reflect your changes if relevant. There is nothing worse than invalid documentation. - Usually, if unit tests are written, pass, and your change is relevant, then your pull request will be merged. Since we're hosted on GitHub, pytest-django uses `git`_ as a version control system. The `GitHub help`_ is very well written and will get you started on using git and GitHub in a jiffy. It is an invaluable resource for newbies and oldtimers alike. Syntax and conventions ====================== We try to conform to `PEP8`_ as much as possible. A few highlights: - Indentation should be exactly 4 spaces. Not 2, not 6, not 8. **4**. Also, tabs are evil. - We try (loosely) to keep the line length at 79 characters. Generally the rule is "it should look good in a terminal-based editor" (eg vim), but we try not be [Godwin's law] about it. Process ======= This is how you fix a bug or add a feature: #. `fork`_ the repository on GitHub. #. Checkout your fork. #. Hack hack hack, test test test, commit commit commit, test again. #. Push to your fork. #. Open a pull request. Tests ===== Having a wide and comprehensive library of unit-tests and integration tests is of exceeding importance. Contributing tests is widely regarded as a very prestigious contribution (you're making everybody's future work much easier by doing so). Good karma for you. Cookie points. Maybe even a beer if we meet in person :) Generally tests should be: - Unitary (as much as possible). I.E. should test as much as possible only on one function/method/class. That's the very definition of unit tests. Integration tests are also interesting obviously, but require more time to maintain since they have a higher probability of breaking. - Short running. No hard numbers here, but if your one test doubles the time it takes for everybody to run them, it's probably an indication that you're doing it wrong. In a similar way to code, pull requests will be reviewed before pulling (obviously), and we encourage discussion via code review (everybody learns something this way) or in the IRC channel. Running the tests ----------------- There is a Makefile in the repository which aids in setting up a virtualenv and running the tests:: $ make test You can manually create the virtualenv using:: $ make testenv This will install a virtualenv with pytest and the latest stable version of Django. The virtualenv can then be activated with:: $ source bin/activate Then, simply invoke pytest to run the test suite:: $ pytest --ds=pytest_django_test.settings_sqlite tox can be used to run the test suite under different configurations by invoking:: $ tox There is a huge number of unique test configurations (98 at the time of writing), running them all will take a long time. All valid configurations can be found in `tox.ini`. To test against a few of them, invoke tox with the `-e` flag:: $ tox -e py36-dj111-postgres,py27-dj111-mysql_innodb This will run the tests on Python 3.6/Django 1.11/PostgeSQL and Python 2.7/Django 1.11/MySQL. Measuring test coverage ----------------------- Some of the tests are executed in subprocesses. Because of that regular coverage measurements (using pytest-cov plugin) are not reliable. If you want to measure coverage you'll need to create .pth file as described in `subprocess section of coverage documentation`_. If you're using ``setup.py develop`` you should uninstall pytest_django (using pip) for the time of measuring coverage. You'll also need mysql and postgres databases. There are predefined settings for each database in the tests directory. You may want to modify these files but please don't include them in your pull requests. After this short initial setup you're ready to run tests:: $ COVERAGE_PROCESS_START=`pwd`/.coveragerc COVERAGE_FILE=`pwd`/.coverage PYTHONPATH=`pwd` pytest --ds=pytest_django_test.settings_postgres You should repeat the above step for sqlite and mysql before the next step. This step will create a lot of ``.coverage`` files with additional suffixes for every process. The final step is to combine all the files created by different processes and generate the html coverage report:: $ coverage combine $ coverage html Your coverage report is now ready in the ``htmlcov`` directory. Continuous integration ---------------------- `GitHub Actions`_ is used to automatically run all tests against all supported versions of Python, Django and different database backends. The `pytest-django Actions`_ page shows the latest test run. The CI will automatically pick up pull requests, test them and report the result directly in the pull request. ************************** Contributing Documentation ************************** Perhaps considered "boring" by hard-core coders, documentation is sometimes even more important than code! This is what brings fresh blood to a project, and serves as a reference for oldtimers. On top of this, documentation is the one area where less technical people can help most - you just need to write a semi-decent English. People need to understand you. We don't care about style or correctness. Documentation should be: - We use `Sphinx`_/`restructuredText`_. So obviously this is the format you should use :) File extensions should be .rst. - Written in English. We can discuss how it would bring more people to the project to have a Klingon translation or anything, but that's a problem we will ask ourselves when we already have a good documentation in English. - Accessible. You should assume the reader to be moderately familiar with Python and Django, but not anything else. Link to documentation of libraries you use, for example, even if they are "obvious" to you (South is the first example that comes to mind - it's obvious to any Django programmer, but not to any newbie at all). A brief description of what it does is also welcome. Pulling of documentation is pretty fast and painless. Usually somebody goes over your text and merges it, since there are no "breaks" and that GitHub parses rst files automagically it's really convenient to work with. Also, contributing to the documentation will earn you great respect from the core developers. You get good karma just like a test contributor, but you get double cookie points. Seriously. You rock. .. note:: This very document is based on the contributing docs of the `django CMS`_ project. Many thanks for allowing us to steal it! .. _fork: https://github.com/pytest-dev/pytest-django .. _issue tracker: https://github.com/pytest-dev/pytest-django/issues .. _Sphinx: https://www.sphinx-doc.org/ .. _PEP8: https://www.python.org/dev/peps/pep-0008/ .. _GitHub : https://www.github.com .. _GitHub help : https://help.github.com .. _freenode : https://freenode.net/ .. _@andreaspelme : https://twitter.com/andreaspelme .. _pull request : https://help.github.com/send-pull-requests/ .. _git : https://git-scm.com/ .. _restructuredText: https://docutils.sourceforge.io/docs/ref/rst/introduction.html .. _django CMS: https://www.django-cms.org/ .. _GitHub Actions: https://github.com/features/actions .. _pytest-django Actions: https://github.com/pytest-dev/pytest-django/actions .. _`subprocess section of coverage documentation`: https://coverage.readthedocs.io/en/latest/subprocess.html pytest-django-4.5.2/docs/database.rst000066400000000000000000000436211415366732400175610ustar00rootroot00000000000000Database access =============== ``pytest-django`` takes a conservative approach to enabling database access. By default your tests will fail if they try to access the database. Only if you explicitly request database access will this be allowed. This encourages you to keep database-needing tests to a minimum which makes it very clear what code uses the database. Enabling database access in tests --------------------------------- You can use :ref:`pytest marks ` to tell ``pytest-django`` your test needs database access:: import pytest @pytest.mark.django_db def test_my_user(): me = User.objects.get(username='me') assert me.is_superuser It is also possible to mark all tests in a class or module at once. This demonstrates all the ways of marking, even though they overlap. Just one of these marks would have been sufficient. See the :ref:`pytest documentation ` for detail:: import pytest pytestmark = pytest.mark.django_db @pytest.mark.django_db class TestUsers: pytestmark = pytest.mark.django_db def test_my_user(self): me = User.objects.get(username='me') assert me.is_superuser By default ``pytest-django`` will set up the Django databases the first time a test needs them. Once setup, the database is cached to be used for all subsequent tests and rolls back transactions, to isolate tests from each other. This is the same way the standard Django :class:`~django.test.TestCase` uses the database. However ``pytest-django`` also caters for transaction test cases and allows you to keep the test databases configured across different test runs. Testing transactions -------------------- Django itself has the :class:`~django.test.TransactionTestCase` which allows you to test transactions and will flush the database between tests to isolate them. The downside of this is that these tests are much slower to set up due to the required flushing of the database. ``pytest-django`` also supports this style of tests, which you can select using an argument to the ``django_db`` mark:: @pytest.mark.django_db(transaction=True) def test_spam(): pass # test relying on transactions .. _`multi-db`: Tests requiring multiple databases ---------------------------------- .. versionadded:: 4.3 .. caution:: This support is **experimental** and is subject to change without deprecation. We are still figuring out the best way to expose this functionality. If you are using this successfully or unsuccessfully, `let us know `_! ``pytest-django`` has experimental support for multi-database configurations. Currently ``pytest-django`` does not specifically support Django's multi-database support, using the ``databases`` argument to the :py:func:`django_db ` mark:: @pytest.mark.django_db(databases=['default', 'other']) def test_spam(): assert MyModel.objects.using('other').count() == 0 For details see :py:attr:`django.test.TransactionTestCase.databases` and :py:attr:`django.test.TestCase.databases`. ``--reuse-db`` - reuse the testing database between test runs -------------------------------------------------------------- Using ``--reuse-db`` will create the test database in the same way as ``manage.py test`` usually does. However, after the test run, the test database will not be removed. The next time a test run is started with ``--reuse-db``, the database will instantly be re used. This will allow much faster startup time for tests. This can be especially useful when running a few tests, when there are a lot of database tables to set up. ``--reuse-db`` will not pick up schema changes between test runs. You must run the tests with ``--reuse-db --create-db`` to re-create the database according to the new schema. Running without ``--reuse-db`` is also possible, since the database will automatically be re-created. ``--create-db`` - force re creation of the test database -------------------------------------------------------- When used with ``--reuse-db``, this option will re-create the database, regardless of whether it exists or not. Example work flow with ``--reuse-db`` and ``--create-db``. ----------------------------------------------------------- A good way to use ``--reuse-db`` and ``--create-db`` can be: * Put ``--reuse-db`` in your default options (in your project's ``pytest.ini`` file):: [pytest] addopts = --reuse-db * Just run tests with ``pytest``, on the first run the test database will be created. The next test run it will be reused. * When you alter your database schema, run ``pytest --create-db``, to force re-creation of the test database. ``--no-migrations`` - Disable Django migrations ----------------------------------------------- Using ``--no-migrations`` (alias: ``--nomigrations``) will disable Django migrations and create the database by inspecting all models. It may be faster when there are several migrations to run in the database setup. You can use ``--migrations`` to force running migrations in case ``--no-migrations`` is used, e.g. in ``setup.cfg``. .. _advanced-database-configuration: Advanced database configuration ------------------------------- pytest-django provides options to customize the way database is configured. The default database construction mostly follows Django's own test runner. You can however influence all parts of the database setup process to make it fit in projects with special requirements. This section assumes some familiarity with the Django test runner, Django database creation and pytest fixtures. Fixtures ######## There are some fixtures which will let you change the way the database is configured in your own project. These fixtures can be overridden in your own project by specifying a fixture with the same name and scope in ``conftest.py``. .. admonition:: Use the pytest-django source code The default implementation of these fixtures can be found in `fixtures.py `_. The code is relatively short and straightforward and can provide a starting point when you need to customize database setup in your own project. django_db_setup """"""""""""""" .. fixture:: django_db_setup This is the top-level fixture that ensures that the test databases are created and available. This fixture is session scoped (it will be run once per test session) and is responsible for making sure the test database is available for tests that need it. The default implementation creates the test database by applying migrations and removes databases after the test run. You can override this fixture in your own ``conftest.py`` to customize how test databases are constructed. django_db_modify_db_settings """""""""""""""""""""""""""" .. fixture:: django_db_modify_db_settings This fixture allows modifying `django.conf.settings.DATABASES `_ just before the databases are configured. If you need to customize the location of your test database, this is the fixture you want to override. The default implementation of this fixture requests the :fixture:`django_db_modify_db_settings_parallel_suffix` to provide compatibility with pytest-xdist. This fixture is by default requested from :fixture:`django_db_setup`. django_db_modify_db_settings_parallel_suffix """""""""""""""""""""""""""""""""""""""""""" .. fixture:: django_db_modify_db_settings_parallel_suffix Requesting this fixture will add a suffix to the database name when the tests are run via `pytest-xdist`, or via `tox` in parallel mode. This fixture is by default requested from :fixture:`django_db_modify_db_settings`. django_db_modify_db_settings_tox_suffix """"""""""""""""""""""""""""""""""""""" .. fixture:: django_db_modify_db_settings_tox_suffix Requesting this fixture will add a suffix to the database name when the tests are run via `tox` in parallel mode. This fixture is by default requested from :fixture:`django_db_modify_db_settings_parallel_suffix`. django_db_modify_db_settings_xdist_suffix """"""""""""""""""""""""""""""""""""""""" .. fixture:: django_db_modify_db_settings_xdist_suffix Requesting this fixture will add a suffix to the database name when the tests are run via `pytest-xdist`. This fixture is by default requested from :fixture:`django_db_modify_db_settings_parallel_suffix`. django_db_use_migrations """""""""""""""""""""""" .. fixture:: django_db_use_migrations Returns whether or not to use migrations to create the test databases. The default implementation returns the value of the ``--migrations``/``--no-migrations`` command line options. This fixture is by default requested from :fixture:`django_db_setup`. django_db_keepdb """""""""""""""" .. fixture:: django_db_keepdb Returns whether or not to re-use an existing database and to keep it after the test run. The default implementation handles the ``--reuse-db`` and ``--create-db`` command line options. This fixture is by default requested from :fixture:`django_db_setup`. django_db_createdb """""""""""""""""" .. fixture:: django_db_createdb Returns whether or not the database is to be re-created before running any tests. This fixture is by default requested from :fixture:`django_db_setup`. django_db_blocker """"""""""""""""" .. fixture:: django_db_blocker .. warning:: It does not manage transactions and changes made to the database will not be automatically restored. Using the ``pytest.mark.django_db`` marker or :fixture:`db` fixture, which wraps database changes in a transaction and restores the state is generally the thing you want in tests. This marker can be used when you are trying to influence the way the database is configured. Database access is by default not allowed. ``django_db_blocker`` is the object which can allow specific code paths to have access to the database. This fixture is used internally to implement the ``db`` fixture. :fixture:`django_db_blocker` can be used as a context manager to enable database access for the specified block:: @pytest.fixture def myfixture(django_db_blocker): with django_db_blocker.unblock(): ... # modify something in the database You can also manage the access manually via these methods: .. py:method:: django_db_blocker.unblock() Enable database access. Should be followed by a call to :func:`~django_db_blocker.restore`. .. py:method:: django_db_blocker.block() Disable database access. Should be followed by a call to :func:`~django_db_blocker.restore`. .. py:function:: django_db_blocker.restore() Restore the previous state of the database blocking. Examples ######## Using a template database for tests """"""""""""""""""""""""""""""""""" This example shows how a pre-created PostgreSQL source database can be copied and used for tests. Put this into ``conftest.py``:: import pytest from django.db import connections import psycopg2 from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT def run_sql(sql): conn = psycopg2.connect(database='postgres') conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) cur = conn.cursor() cur.execute(sql) conn.close() @pytest.fixture(scope='session') def django_db_setup(): from django.conf import settings settings.DATABASES['default']['NAME'] = 'the_copied_db' run_sql('DROP DATABASE IF EXISTS the_copied_db') run_sql('CREATE DATABASE the_copied_db TEMPLATE the_source_db') yield for connection in connections.all(): connection.close() run_sql('DROP DATABASE the_copied_db') Using an existing, external database for tests """""""""""""""""""""""""""""""""""""""""""""" This example shows how you can connect to an existing database and use it for your tests. This example is trivial, you just need to disable all of pytest-django and Django's test database creation and point to the existing database. This is achieved by simply implementing a no-op :fixture:`django_db_setup` fixture. Put this into ``conftest.py``:: import pytest @pytest.fixture(scope='session') def django_db_setup(): settings.DATABASES['default'] = { 'ENGINE': 'django.db.backends.mysql', 'HOST': 'db.example.com', 'NAME': 'external_db', } Populate the database with initial test data """""""""""""""""""""""""""""""""""""""""""" In some cases you want to populate the test database before you start the tests. Because of different ways you may use the test database, there are different ways to populate it. Populate the test database if you don't use transactional or live_server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you are using the :func:`pytest.mark.django_db` marker or :fixture:`db` fixture, you probably don't want to explicitly handle transactions in your tests. In this case, it is sufficient to populate your database only once. You can put code like this in ``conftest.py``:: import pytest from django.core.management import call_command @pytest.fixture(scope='session') def django_db_setup(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): call_command('loaddata', 'my_fixture.json') This loads the Django fixture ``my_fixture.json`` once for the entire test session. This data will be available to tests marked with the :func:`pytest.mark.django_db` mark, or tests which use the :fixture:`db` fixture. The test data will be saved in the database and will not be reset. This example uses Django's fixture loading mechanism, but it can be replaced with any way of loading data into the database. Notice :fixture:`django_db_setup` in the argument list. This triggers the original pytest-django fixture to create the test database, so that when ``call_command`` is invoked, the test database is already prepared and configured. Populate the test database if you use transactional or live_server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In case you use transactional tests (you use the :func:`pytest.mark.django_db` marker with ``transaction=True``, or the :fixture:`transactional_db` fixture), you need to repopulate your database every time a test starts, because the database is cleared between tests. The :fixture:`live_server` fixture uses :fixture:`transactional_db`, so you also need to populate the test database this way when using it. You can put this code into ``conftest.py``. Note that while it it is similar to the previous one, the scope is changed from ``session`` to ``function``:: import pytest from myapp.models import Widget @pytest.fixture(scope='function') def django_db_setup(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): Widget.objects.create(...) Use the same database for all xdist processes """"""""""""""""""""""""""""""""""""""""""""" By default, each xdist process gets its own database to run tests on. This is needed to have transactional tests that do not interfere with each other. If you instead want your tests to use the same database, override the :fixture:`django_db_modify_db_settings` to not do anything. Put this in ``conftest.py``:: import pytest @pytest.fixture(scope='session') def django_db_modify_db_settings(): pass Randomize database sequences """""""""""""""""""""""""""" You can customize the test database after it has been created by extending the :fixture:`django_db_setup` fixture. This example shows how to give a PostgreSQL sequence a random starting value. This can be used to detect and prevent primary key id's from being hard-coded in tests. Put this in ``conftest.py``:: import random import pytest from django.db import connection @pytest.fixture(scope='session') def django_db_setup(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): cur = connection.cursor() cur.execute('ALTER SEQUENCE app_model_id_seq RESTART WITH %s;', [random.randint(10000, 20000)]) Create the test database from a custom SQL script """"""""""""""""""""""""""""""""""""""""""""""""" You can replace the :fixture:`django_db_setup` fixture and run any code in its place. This includes creating your database by hand by running a SQL script directly. This example shows sqlite3's executescript method. In a more general use case, you probably want to load the SQL statements from a file or invoke the ``psql`` or the ``mysql`` command line tool. Put this in ``conftest.py``:: import pytest from django.db import connection @pytest.fixture(scope='session') def django_db_setup(django_db_blocker): with django_db_blocker.unblock(): with connection.cursor() as c: c.executescript(''' DROP TABLE IF EXISTS theapp_item; CREATE TABLE theapp_item (id, name); INSERT INTO theapp_item (name) VALUES ('created from a sql script'); ''') .. warning:: This snippet shows ``cursor().executescript()`` which is `sqlite` specific, for other database engines this method might differ. For instance, psycopg2 uses ``cursor().execute()``. Use a read only database """""""""""""""""""""""" You can replace the ordinary `django_db_setup` to completely avoid database creation/migrations. If you have no need for rollbacks or truncating tables, you can simply avoid blocking the database and use it directly. When using this method you must ensure that your tests do not change the database state. Put this in ``conftest.py``:: import pytest @pytest.fixture(scope='session') def django_db_setup(): """Avoid creating/setting up the test database""" pass @pytest.fixture def db_access_without_rollback_and_truncate(request, django_db_setup, django_db_blocker): django_db_blocker.unblock() request.addfinalizer(django_db_blocker.restore) pytest-django-4.5.2/docs/faq.rst000066400000000000000000000125001415366732400165540ustar00rootroot00000000000000FAQ === .. _faq-import-error: I see an error saying "could not import myproject.settings" ----------------------------------------------------------- pytest-django tries to automatically add your project to the Python path by looking for a ``manage.py`` file and adding its path to the Python path. If this for some reason fails for you, you have to manage your Python paths explicitly. See the documentation on :ref:`managing_the_python_path_explicitly` for more information. How can I make sure that all my tests run with a specific locale? ----------------------------------------------------------------- Create a :ref:`pytest fixture ` that is automatically run before each test case. To run all tests with the English locale, put the following code in your project's :ref:`conftest.py ` file: .. code-block:: python from django.utils.translation import activate @pytest.fixture(autouse=True) def set_default_language(): activate('en') .. _faq-tests-not-being-picked-up: My tests are not being found. Why? ---------------------------------- By default, pytest looks for tests in files named ``test_*.py`` (note that this is not the same as ``test*.py``) and ``*_test.py``. If you have your tests in files with other names, they will not be collected. Note that Django's ``startapp`` manage command creates an ``app_dir/tests.py`` file. Also, it is common to put tests under ``app_dir/tests/views.py``, etc. To find those tests, create a ``pytest.ini`` file in your project root and add an appropriate ``python_files`` line to it: .. code-block:: ini [pytest] python_files = tests.py test_*.py *_tests.py See the `related pytest docs`_ for more details. When debugging test collection problems, the ``--collectonly`` flag and ``-rs`` (report skipped tests) can be helpful. .. _related pytest docs: https://docs.pytest.org/en/stable/example/pythoncollection.html#changing-naming-conventions Does pytest-django work with the pytest-xdist plugin? ----------------------------------------------------- Yes. pytest-django supports running tests in parallel with pytest-xdist. Each process created by xdist gets its own separate database that is used for the tests. This ensures that each test can run independently, regardless of whether transactions are tested or not. .. _faq-getting-help: How can I use ``manage.py test`` with pytest-django? ---------------------------------------------------- pytest-django is designed to work with the ``pytest`` command, but if you really need integration with ``manage.py test``, you can create a simple test runner like this: .. code-block:: python class PytestTestRunner: """Runs pytest to discover and run tests.""" def __init__(self, verbosity=1, failfast=False, keepdb=False, **kwargs): self.verbosity = verbosity self.failfast = failfast self.keepdb = keepdb @classmethod def add_arguments(cls, parser): parser.add_argument( '--keepdb', action='store_true', help='Preserves the test DB between runs.' ) def run_tests(self, test_labels): """Run pytest and return the exitcode. It translates some of Django's test command option to pytest's. """ import pytest argv = [] if self.verbosity == 0: argv.append('--quiet') if self.verbosity == 2: argv.append('--verbose') if self.verbosity == 3: argv.append('-vv') if self.failfast: argv.append('--exitfirst') if self.keepdb: argv.append('--reuse-db') argv.extend(test_labels) return pytest.main(argv) Add the path to this class in your Django settings: .. code-block:: python TEST_RUNNER = 'my_project.runner.PytestTestRunner' Usage: .. code-block:: bash ./manage.py test -- **Note**: the pytest-django command line options ``--ds`` and ``--dc`` are not compatible with this approach, you need to use the standard Django methods of setting the ``DJANGO_SETTINGS_MODULE``/``DJANGO_CONFIGURATION`` environmental variables or the ``--settings`` command line option. How can I give database access to all my tests without the `django_db` marker? ------------------------------------------------------------------------------ Create an autouse fixture and put it in ``conftest.py`` in your project root: .. code-block:: python @pytest.fixture(autouse=True) def enable_db_access_for_all_tests(db): pass How/where can I get help with pytest/pytest-django? --------------------------------------------------- Usage questions can be asked on StackOverflow with the `pytest tag`_. If you think you've found a bug or something that is wrong in the documentation, feel free to `open an issue on the GitHub project`_ for pytest-django. Direct help can be found in the #pytest IRC channel `on irc.libera.chat `_ (using an IRC client, `via webchat `_, or `via Matrix `_). .. _pytest tag: https://stackoverflow.com/search?q=pytest .. _open an issue on the GitHub project: https://github.com/pytest-dev/pytest-django/issues/ pytest-django-4.5.2/docs/helpers.rst000066400000000000000000000435621415366732400174630ustar00rootroot00000000000000.. _helpers: Django helpers ============== Assertions ---------- All of Django's :py:class:`~django:django.test.TestCase` :ref:`django:assertions` are available in ``pytest_django.asserts``, e.g. :: from pytest_django.asserts import assertTemplateUsed Markers ------- ``pytest-django`` registers and uses markers. See the pytest :ref:`documentation ` on what marks are and for notes on :ref:`using ` them. Remember that you can apply marks at the single test level, the class level, the module level, and dynamically in a hook or fixture. ``pytest.mark.django_db`` - request database access ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. py:function:: pytest.mark.django_db([transaction=False, reset_sequences=False, databases=None]) This is used to mark a test function as requiring the database. It will ensure the database is set up correctly for the test. Each test will run in its own transaction which will be rolled back at the end of the test. This behavior is the same as Django's standard :class:`~django.test.TestCase` class. In order for a test to have access to the database it must either be marked using the :func:`~pytest.mark.django_db` mark or request one of the :fixture:`db`, :fixture:`transactional_db` or :fixture:`django_db_reset_sequences` fixtures. Otherwise the test will fail when trying to access the database. :type transaction: bool :param transaction: The ``transaction`` argument will allow the test to use real transactions. With ``transaction=False`` (the default when not specified), transaction operations are noops during the test. This is the same behavior that :class:`django.test.TestCase` uses. When ``transaction=True``, the behavior will be the same as :class:`django.test.TransactionTestCase`. :type reset_sequences: bool :param reset_sequences: The ``reset_sequences`` argument will ask to reset auto increment sequence values (e.g. primary keys) before running the test. Defaults to ``False``. Must be used together with ``transaction=True`` to have an effect. Please be aware that not all databases support this feature. For details see :py:attr:`django.test.TransactionTestCase.reset_sequences`. :type databases: Union[Iterable[str], str, None] :param databases: .. caution:: This argument is **experimental** and is subject to change without deprecation. We are still figuring out the best way to expose this functionality. If you are using this successfully or unsuccessfully, `let us know `_! The ``databases`` argument defines which databases in a multi-database configuration will be set up and may be used by the test. Defaults to only the ``default`` database. The special value ``"__all__"`` may be use to specify all configured databases. For details see :py:attr:`django.test.TransactionTestCase.databases` and :py:attr:`django.test.TestCase.databases`. :type serialized_rollback: bool :param serialized_rollback: The ``serialized_rollback`` argument enables :ref:`rollback emulation `. After a transactional test (or any test using a database backend which doesn't support transactions) runs, the database is flushed, destroying data created in data migrations. Setting ``serialized_rollback=True`` tells Django to serialize the database content during setup, and restore it during teardown. Note that this will slow down that test suite by approximately 3x. .. note:: If you want access to the Django database inside a *fixture*, this marker may or may not help even if the function requesting your fixture has this marker applied, depending on pytest's fixture execution order. To access the database in a fixture, it is recommended that the fixture explicitly request one of the :fixture:`db`, :fixture:`transactional_db`, :fixture:`django_db_reset_sequences` or :fixture:`django_db_serialized_rollback` fixtures. See below for a description of them. .. note:: Automatic usage with ``django.test.TestCase``. Test classes that subclass :class:`django.test.TestCase` will have access to the database always to make them compatible with existing Django tests. Test classes that subclass Python's :class:`unittest.TestCase` need to have the marker applied in order to access the database. ``pytest.mark.urls`` - override the urlconf ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. py:function:: pytest.mark.urls(urls) Specify a different ``settings.ROOT_URLCONF`` module for the marked tests. :type urls: str :param urls: The urlconf module to use for the test, e.g. ``myapp.test_urls``. This is similar to Django's ``TestCase.urls`` attribute. Example usage:: @pytest.mark.urls('myapp.test_urls') def test_something(client): assert 'Success!' in client.get('/some_url_defined_in_test_urls/').content ``pytest.mark.ignore_template_errors`` - ignore invalid template variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. py:function:: pytest.mark.ignore_template_errors Ignore errors when using the ``--fail-on-template-vars`` option, i.e. do not cause tests to fail if your templates contain invalid variables. This marker sets the ``string_if_invalid`` template option. See :ref:`django:invalid-template-variables`. Example usage:: @pytest.mark.ignore_template_errors def test_something(client): client('some-url-with-invalid-template-vars') Fixtures -------- pytest-django provides some pytest fixtures to provide dependencies for tests. More information on fixtures is available in the :ref:`pytest documentation `. .. fixture:: rf ``rf`` - ``RequestFactory`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ An instance of a :class:`django.test.RequestFactory`. Example """"""" :: from myapp.views import my_view def test_details(rf, admin): request = rf.get('/customer/details') # Remember that when using RequestFactory, the request does not pass # through middleware. If your view expects fields such as request.user # to be set, you need to set them explicitly. # The following line sets request.user to an admin user. request.user = admin response = my_view(request) assert response.status_code == 200 .. fixture:: async_rf ``async_rf`` - ``AsyncRequestFactory`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An instance of a `django.test.AsyncRequestFactory`_. .. _django.test.AsyncRequestFactory: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#asyncrequestfactory Example """"""" This example uses `pytest-asyncio `_. :: from myapp.views import my_view @pytest.mark.asyncio async def test_details(async_rf): request = await async_rf.get('/customer/details') response = my_view(request) assert response.status_code == 200 .. fixture:: client ``client`` - ``django.test.Client`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An instance of a :class:`django.test.Client`. Example """"""" :: def test_with_client(client): response = client.get('/') assert response.content == 'Foobar' To use `client` as an authenticated standard user, call its :meth:`force_login() ` or :meth:`login() ` method before accessing a URL: :: def test_with_authenticated_client(client, django_user_model): username = "user1" password = "bar" user = django_user_model.objects.create_user(username=username, password=password) # Use this: client.force_login(user) # Or this: client.login(username=username, password=password) response = client.get('/private') assert response.content == 'Protected Area' .. fixture:: async_client ``async_client`` - ``django.test.AsyncClient`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An instance of a `django.test.AsyncClient`_. .. _django.test.AsyncClient: https://docs.djangoproject.com/en/stable/topics/testing/tools/#testing-asynchronous-code Example """"""" This example uses `pytest-asyncio `_. :: @pytest.mark.asyncio async def test_with_async_client(async_client): response = await async_client.get('/') assert response.content == 'Foobar' .. fixture:: admin_client ``admin_client`` - ``django.test.Client`` logged in as admin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An instance of a :class:`django.test.Client`, logged in as an admin user. Example """"""" :: def test_an_admin_view(admin_client): response = admin_client.get('/admin/') assert response.status_code == 200 Using the `admin_client` fixture will cause the test to automatically be marked for database use (no need to specify the :func:`~pytest.mark.django_db` mark). .. fixture:: admin_user ``admin_user`` - an admin user (superuser) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An instance of a superuser, with username "admin" and password "password" (in case there is no "admin" user yet). Using the `admin_user` fixture will cause the test to automatically be marked for database use (no need to specify the :func:`~pytest.mark.django_db` mark). .. fixture:: django_user_model ``django_user_model`` ~~~~~~~~~~~~~~~~~~~~~ A shortcut to the User model configured for use by the current Django project (aka the model referenced by `settings.AUTH_USER_MODEL `_). Use this fixture to make pluggable apps testable regardless what User model is configured in the containing Django project. Example """"""" :: def test_new_user(django_user_model): django_user_model.objects.create(username="someone", password="something") .. fixture:: django_username_field ``django_username_field`` ~~~~~~~~~~~~~~~~~~~~~~~~~ This fixture extracts the field name used for the username on the user model, i.e. resolves to the user model's :attr:`~django.contrib.auth.models.CustomUser.USERNAME_FIELD`. Use this fixture to make pluggable apps testable regardless what the username field is configured to be in the containing Django project. .. fixture:: db ``db`` ~~~~~~~ This fixture will ensure the Django database is set up. Only required for fixtures that want to use the database themselves. A test function should normally use the :func:`pytest.mark.django_db` mark to signal it needs the database. This fixture does not return a database connection object. When you need a Django database connection or cursor, import it from Django using ``from django.db import connection``. .. fixture:: transactional_db ``transactional_db`` ~~~~~~~~~~~~~~~~~~~~ This fixture can be used to request access to the database including transaction support. This is only required for fixtures which need database access themselves. A test function should normally use the :func:`pytest.mark.django_db` mark with ``transaction=True`` to signal it needs the database. .. fixture:: django_db_reset_sequences ``django_db_reset_sequences`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This fixture provides the same transactional database access as :fixture:`transactional_db`, with additional support for reset of auto increment sequences (if your database supports it). This is only required for fixtures which need database access themselves. A test function should normally use the :func:`pytest.mark.django_db` mark with ``transaction=True`` and ``reset_sequences=True``. .. fixture:: django_db_serialized_rollback ``django_db_serialized_rollback`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This fixture triggers :ref:`rollback emulation `. This is only required for fixtures which need to enforce this behavior. A test function should normally use :func:`pytest.mark.django_db` with ``serialized_rollback=True`` (and most likely also ``transaction=True``) to request this behavior. .. fixture:: live_server ``live_server`` ~~~~~~~~~~~~~~~ This fixture runs a live Django server in a background thread. The server's URL can be retrieved using the ``live_server.url`` attribute or by requesting it's string value: ``str(live_server)``. You can also directly concatenate a string to form a URL: ``live_server + '/foo'``. Since the live server and the tests run in different threads, they cannot share a database transaction. For this reason, ``live_server`` depends on the ``transactional_db`` fixture. If tests depend on data created in data migrations, you should add the ``django_db_serialized_rollback`` fixture. .. note:: Combining database access fixtures. When using multiple database fixtures together, only one of them is used. Their order of precedence is as follows (the last one wins): * ``db`` * ``transactional_db`` In addition, using ``live_server`` or ``django_db_reset_sequences`` will also trigger transactional database access, and ``django_db_serialized_rollback`` regular database access, if not specified. .. fixture:: settings ``settings`` ~~~~~~~~~~~~ This fixture will provide a handle on the Django settings module, and automatically revert any changes made to the settings (modifications, additions and deletions). Example """"""" :: def test_with_specific_settings(settings): settings.USE_TZ = True assert settings.USE_TZ .. fixture:: django_assert_num_queries ``django_assert_num_queries`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. py:function:: django_assert_num_queries(num, connection=None, info=None) :param num: expected number of queries :param connection: optional non-default DB connection :param str info: optional info message to display on failure This fixture allows to check for an expected number of DB queries. If the assertion failed, the executed queries can be shown by using the verbose command line option. It wraps ``django.test.utils.CaptureQueriesContext`` and yields the wrapped ``CaptureQueriesContext`` instance. Example usage:: def test_queries(django_assert_num_queries): with django_assert_num_queries(3) as captured: Item.objects.create('foo') Item.objects.create('bar') Item.objects.create('baz') assert 'foo' in captured.captured_queries[0]['sql'] .. fixture:: django_assert_max_num_queries ``django_assert_max_num_queries`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. py:function:: django_assert_max_num_queries(num, connection=None, info=None) :param num: expected maximum number of queries :param connection: optional non-default DB connection :param str info: optional info message to display on failure This fixture allows to check for an expected maximum number of DB queries. It is a specialized version of :fixture:`django_assert_num_queries`. Example usage:: def test_max_queries(django_assert_max_num_queries): with django_assert_max_num_queries(2): Item.objects.create('foo') Item.objects.create('bar') .. fixture:: django_capture_on_commit_callbacks ``django_capture_on_commit_callbacks`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. py:function:: django_capture_on_commit_callbacks(*, using=DEFAULT_DB_ALIAS, execute=False) :param using: The alias of the database connection to capture callbacks for. :param execute: If True, all the callbacks will be called as the context manager exits, if no exception occurred. This emulates a commit after the wrapped block of code. .. versionadded:: 4.4 Returns a context manager that captures :func:`transaction.on_commit() ` callbacks for the given database connection. It returns a list that contains, on exit of the context, the captured callback functions. From this list you can make assertions on the callbacks or call them to invoke their side effects, emulating a commit. Avoid this fixture in tests using ``transaction=True``; you are not likely to get useful results. This fixture is based on Django's :meth:`django.test.TestCase.captureOnCommitCallbacks` helper. Example usage:: def test_on_commit(client, mailoutbox, django_capture_on_commit_callbacks): with django_capture_on_commit_callbacks(execute=True) as callbacks: response = client.post( '/contact/', {'message': 'I like your site'}, ) assert response.status_code == 200 assert len(callbacks) == 1 assert len(mailoutbox) == 1 assert mailoutbox[0].subject == 'Contact Form' assert mailoutbox[0].body == 'I like your site' .. fixture:: mailoutbox ``mailoutbox`` ~~~~~~~~~~~~~~ A clean email outbox to which Django-generated emails are sent. Example """"""" :: from django.core import mail def test_mail(mailoutbox): mail.send_mail('subject', 'body', 'from@example.com', ['to@example.com']) assert len(mailoutbox) == 1 m = mailoutbox[0] assert m.subject == 'subject' assert m.body == 'body' assert m.from_email == 'from@example.com' assert list(m.to) == ['to@example.com'] This uses the ``django_mail_patch_dns`` fixture, which patches ``DNS_NAME`` used by :py:mod:`django.core.mail` with the value from the ``django_mail_dnsname`` fixture, which defaults to "fake-tests.example.com". Automatic cleanup ----------------- pytest-django provides some functionality to assure a clean and consistent environment during tests. Clearing of site cache ~~~~~~~~~~~~~~~~~~~~~~ If ``django.contrib.sites`` is in your INSTALLED_APPS, Site cache will be cleared for each test to avoid hitting the cache and causing the wrong Site object to be returned by ``Site.objects.get_current()``. Clearing of mail.outbox ~~~~~~~~~~~~~~~~~~~~~~~ ``mail.outbox`` will be cleared for each pytest, to give each new test an empty mailbox to work with. However, it's more "pytestic" to use the ``mailoutbox`` fixture described above than to access ``mail.outbox``. pytest-django-4.5.2/docs/index.rst000066400000000000000000000037511415366732400171240ustar00rootroot00000000000000=========================== pytest-django Documentation =========================== pytest-django is a plugin for `pytest`_ that provides a set of useful tools for testing `Django`_ applications and projects. .. _pytest: https://pytest.org/ .. _Django: https://www.djangoproject.com/ Quick Start =========== .. code-block:: bash $ pip install pytest-django Make sure ``DJANGO_SETTINGS_MODULE`` is defined (see :ref:`configuring_django_settings`) and make your tests discoverable (see :ref:`faq-tests-not-being-picked-up`): .. code-block:: ini # -- FILE: pytest.ini (or tox.ini) [pytest] DJANGO_SETTINGS_MODULE = test.settings # -- recommended but optional: python_files = tests.py test_*.py *_tests.py Run your tests with ``pytest``: .. code-block:: bash $ pytest Why would I use this instead of Django's manage.py test command? ================================================================ Running the test suite with pytest offers some features that are not present in Django's standard test mechanism: * Less boilerplate: no need to import unittest, create a subclass with methods. Just write tests as regular functions. * :ref:`Manage test dependencies with fixtures `. * Run tests in multiple processes for increased speed. * There are a lot of other nice plugins available for pytest. * Easy switching: Existing unittest-style tests will still work without any modifications. See the `pytest documentation`_ for more information on pytest. .. _pytest documentation: https://docs.pytest.org/ Bugs? Feature Suggestions? ========================== Report issues and feature requests at the `GitHub issue tracker`_. .. _GitHub issue tracker: https://github.com/pytest-dev/pytest-django/issues Table of Contents ================= .. toctree:: :maxdepth: 3 tutorial configuring_django managing_python_path usage database helpers faq contributing changelog Indices and Tables ================== * :ref:`genindex` * :ref:`modindex` pytest-django-4.5.2/docs/make.bat000066400000000000000000000117661415366732400166750ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pytest-django.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pytest-django.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pytest-django-4.5.2/docs/managing_python_path.rst000066400000000000000000000056001415366732400222060ustar00rootroot00000000000000.. _managing_python_path: Managing the Python path ======================== pytest needs to be able to import the code in your project. Normally, when interacting with Django code, the interaction happens via ``manage.py``, which will implicitly add that directory to the Python path. However, when Python is started via the ``pytest`` command, some extra care is needed to have the Python path setup properly. There are two ways to handle this problem, described below. Automatic looking for of Django projects ---------------------------------------- By default, pytest-django tries to find Django projects by automatically looking for the project's ``manage.py`` file and adding its directory to the Python path. Looking for the ``manage.py`` file uses the same algorithm as pytest uses to find ``pytest.ini``, ``tox.ini`` and ``setup.cfg``: Each test root directories parents will be searched for ``manage.py`` files, and it will stop when the first file is found. If you have a custom project setup, have none or multiple ``manage.py`` files in your project, the automatic detection may not be correct. See :ref:`managing_the_python_path_explicitly` for more details on how to configure your environment in that case. .. _managing_the_python_path_explicitly: Managing the Python path explicitly ----------------------------------- First, disable the automatic Django project finder. Add this to ``pytest.ini``, ``setup.cfg`` or ``tox.ini``:: [pytest] django_find_project = false Next, you need to make sure that your project code is available on the Python path. There are multiple ways to achieve this: Managing your project with virtualenv, pip and editable mode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The easiest way to have your code available on the Python path when using virtualenv and pip is to have a setup.py file and install your project in editable mode when developing. If you don't already have a setup.py file, creating a setup.py file with this content will get you started:: import setuptools setuptools.setup(name='myproj', version='1.0') This ``setup.py`` file is not sufficient to distribute your package to PyPI or more general packaging, but it should help you get started. Please refer to the `Python Packaging User Guide `_ for more information on packaging Python applications.` To install the project afterwards:: pip install --editable . Your code should then be importable from any Python application. You can also add this directly to your project's requirements.txt file like this:: # requirements.txt -e . django>=1.11 pytest-django Using pytest-pythonpath ~~~~~~~~~~~~~~~~~~~~~~~ You can also use the `pytest-pythonpath `_ plugin to explicitly add paths to the Python path. pytest-django-4.5.2/docs/tutorial.rst000066400000000000000000000064631415366732400176630ustar00rootroot00000000000000Getting started with pytest and pytest-django ============================================= Introduction ------------ pytest and pytest-django are compatible with standard Django test suites and Nose test suites. They should be able to pick up and run existing tests without any or little configuration. This section describes how to get started quickly. Talks, articles and blog posts ------------------------------ * Talk from DjangoCon Europe 2014: `pytest: helps you write better Django apps, by Andreas Pelme `_ * Talk from EuroPython 2013: `Testing Django application with pytest, by Andreas Pelme `_ * Three part blog post tutorial (part 3 mentions Django integration): `pytest: no-boilerplate testing, by Daniel Greenfeld `_ * Blog post: `Django Projects to Django Apps: Converting the Unit Tests, by John Costa `_. For general information and tutorials on pytest, see the `pytest tutorial page `_. Step 1: Installation -------------------- pytest-django can be obtained directly from `PyPI `_, and can be installed with ``pip``: .. code-block:: bash pip install pytest-django Installing pytest-django will also automatically install the latest version of pytest. ``pytest-django`` uses ``pytest``'s plugin system and can be used right away after installation, there is nothing more to configure. Step 2: Point pytest to your Django settings -------------------------------------------- You need to tell pytest which Django settings should be used for test runs. The easiest way to achieve this is to create a pytest configuration file with this information. Create a file called ``pytest.ini`` in your project root directory that contains: .. code-block:: ini [pytest] DJANGO_SETTINGS_MODULE = yourproject.settings You can also specify your Django settings by setting the ``DJANGO_SETTINGS_MODULE`` environment variable or specifying the ``--ds=yourproject.settings`` command line flag when running the tests. See the full documentation on :ref:`configuring_django_settings`. Optionally, also add the following line to the ``[pytest]`` section to instruct pytest to collect tests in Django's default app layouts, too. See the FAQ at :ref:`faq-tests-not-being-picked-up` for more infos. .. code-block:: ini python_files = tests.py test_*.py *_tests.py Step 3: Run your test suite --------------------------- Tests are invoked directly with the ``pytest`` command, instead of ``manage.py test``, that you might be used to: .. code-block:: bash pytest Do you have problems with pytest not finding your code? See the FAQ :ref:`faq-import-error`. Next steps ---------- The :ref:`usage` section describes more ways to interact with your test suites. pytest-django also provides some :ref:`helpers` to make it easier to write Django tests. Consult the `pytest documentation `_ for more information on pytest itself. Stuck? Need help? ----------------- No problem, see the FAQ on :ref:`faq-getting-help` for information on how to get help. pytest-django-4.5.2/docs/usage.rst000066400000000000000000000051171415366732400171170ustar00rootroot00000000000000.. _usage: Usage and invocations ===================== Basic usage ----------- When using pytest-django, django-admin.py or manage.py is not used to run tests. This makes it possible to invoke pytest and other plugins with all its different options directly. Running a test suite is done by invoking the pytest command directly:: pytest Specific test files or directories can be selected by specifying the test file names directly on the command line:: pytest test_something.py a_directory See the `pytest documentation on Usage and invocations `_ for more help on available parameters. Additional command line options ------------------------------- ``--fail-on-template-vars`` - fail for invalid variables in templates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Fail tests that render templates which make use of invalid template variables. You can switch it on in `pytest.ini`:: [pytest] FAIL_INVALID_TEMPLATE_VARS = True Additional pytest.ini settings ------------------------------ ``django_debug_mode`` - change how DEBUG is set ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default tests run with the `DEBUG `_ setting set to ``False``. This is to ensure that the observed output of your code matches what will be seen in a production setting. If you want ``DEBUG`` to be set:: [pytest] django_debug_mode = true You can also use ``django_debug_mode = keep`` to disable the overriding and use whatever is already set in the Django settings. Running tests in parallel with pytest-xdist ------------------------------------------- pytest-django supports running tests on multiple processes to speed up test suite run time. This can lead to significant speed improvements on multi core/multi CPU machines. This requires the pytest-xdist plugin to be available, it can usually be installed with:: pip install pytest-xdist You can then run the tests by running:: pytest -n When tests are invoked with xdist, pytest-django will create a separate test database for each process. Each test database will be given a suffix (something like "gw0", "gw1") to map to a xdist process. If your database name is set to "foo", the test database with xdist will be "test_foo_gw0", "test_foo_gw1" etc. See the full documentation on `pytest-xdist `_ for more information. Among other features, pytest-xdist can distribute/coordinate test execution on remote machines. pytest-django-4.5.2/pyproject.toml000066400000000000000000000003771415366732400172500ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=45.0", # sync with setup.cfg until we discard non-pep-517/518 "setuptools-scm[toml]>=5.0.0", "wheel", ] build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "pytest_django/_version.py" pytest-django-4.5.2/pytest_django/000077500000000000000000000000001415366732400171775ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django/__init__.py000066400000000000000000000003131415366732400213050ustar00rootroot00000000000000try: from ._version import version as __version__ except ImportError: # pragma: no cover # Broken installation, we don't even try. __version__ = "unknown" __all__ = ( "__version__", ) pytest-django-4.5.2/pytest_django/asserts.py000066400000000000000000000107531415366732400212430ustar00rootroot00000000000000""" Dynamically load all Django assertion cases and expose them for importing. """ from functools import wraps from typing import Any, Callable, Optional, Sequence, Set, Union from django.test import ( LiveServerTestCase, SimpleTestCase, TestCase, TransactionTestCase, ) TYPE_CHECKING = False test_case = TestCase("run") def _wrapper(name: str): func = getattr(test_case, name) @wraps(func) def assertion_func(*args, **kwargs): return func(*args, **kwargs) return assertion_func __all__ = [] assertions_names = set() # type: Set[str] assertions_names.update( {attr for attr in vars(TestCase) if attr.startswith("assert")}, {attr for attr in vars(SimpleTestCase) if attr.startswith("assert")}, {attr for attr in vars(LiveServerTestCase) if attr.startswith("assert")}, {attr for attr in vars(TransactionTestCase) if attr.startswith("assert")}, ) for assert_func in assertions_names: globals()[assert_func] = _wrapper(assert_func) __all__.append(assert_func) if TYPE_CHECKING: from django.http import HttpResponse def assertRedirects( response: HttpResponse, expected_url: str, status_code: int = ..., target_status_code: int = ..., msg_prefix: str = ..., fetch_redirect_response: bool = ..., ) -> None: ... def assertURLEqual( url1: str, url2: str, msg_prefix: str = ..., ) -> None: ... def assertContains( response: HttpResponse, text: object, count: Optional[int] = ..., status_code: int = ..., msg_prefix: str = ..., html: bool = False, ) -> None: ... def assertNotContains( response: HttpResponse, text: object, status_code: int = ..., msg_prefix: str = ..., html: bool = False, ) -> None: ... def assertFormError( response: HttpResponse, form: str, field: Optional[str], errors: Union[str, Sequence[str]], msg_prefix: str = ..., ) -> None: ... def assertFormsetError( response: HttpResponse, formset: str, form_index: Optional[int], field: Optional[str], errors: Union[str, Sequence[str]], msg_prefix: str = ..., ) -> None: ... def assertTemplateUsed( response: Optional[HttpResponse] = ..., template_name: Optional[str] = ..., msg_prefix: str = ..., count: Optional[int] = ..., ): ... def assertTemplateNotUsed( response: Optional[HttpResponse] = ..., template_name: Optional[str] = ..., msg_prefix: str = ..., ): ... def assertRaisesMessage( expected_exception: BaseException, expected_message: str, *args, **kwargs ): ... def assertWarnsMessage( expected_warning: Warning, expected_message: str, *args, **kwargs ): ... def assertFieldOutput( fieldclass, valid, invalid, field_args=..., field_kwargs=..., empty_value: str = ..., ) -> None: ... def assertHTMLEqual( html1: str, html2: str, msg: Optional[str] = ..., ) -> None: ... def assertHTMLNotEqual( html1: str, html2: str, msg: Optional[str] = ..., ) -> None: ... def assertInHTML( needle: str, haystack: str, count: Optional[int] = ..., msg_prefix: str = ..., ) -> None: ... def assertJSONEqual( raw: str, expected_data: Any, msg: Optional[str] = ..., ) -> None: ... def assertJSONNotEqual( raw: str, expected_data: Any, msg: Optional[str] = ..., ) -> None: ... def assertXMLEqual( xml1: str, xml2: str, msg: Optional[str] = ..., ) -> None: ... def assertXMLNotEqual( xml1: str, xml2: str, msg: Optional[str] = ..., ) -> None: ... def assertQuerysetEqual( qs, values, transform=..., ordered: bool = ..., msg: Optional[str] = ..., ) -> None: ... def assertNumQueries( num: int, func=..., *args, using: str = ..., **kwargs ): ... # Fallback in case Django adds new asserts. def __getattr__(name: str) -> Callable[..., Any]: ... pytest-django-4.5.2/pytest_django/django_compat.py000066400000000000000000000006511415366732400223600ustar00rootroot00000000000000# Note that all functions here assume django is available. So ensure # this is the case before you call them. def is_django_unittest(request_or_item) -> bool: """Returns True if the request_or_item is a Django test case, otherwise False""" from django.test import SimpleTestCase cls = getattr(request_or_item, "cls", None) if cls is None: return False return issubclass(cls, SimpleTestCase) pytest-django-4.5.2/pytest_django/fixtures.py000066400000000000000000000472731415366732400214370ustar00rootroot00000000000000"""All pytest-django fixtures""" import os from contextlib import contextmanager from functools import partial from typing import ( Any, Callable, Generator, Iterable, List, Optional, Tuple, Union, ) import pytest from . import live_server_helper from .django_compat import is_django_unittest from .lazy_django import get_django_version, skip_if_no_django TYPE_CHECKING = False if TYPE_CHECKING: from typing import Literal import django _DjangoDbDatabases = Optional[Union["Literal['__all__']", Iterable[str]]] # transaction, reset_sequences, databases, serialized_rollback _DjangoDb = Tuple[bool, bool, _DjangoDbDatabases, bool] __all__ = [ "django_db_setup", "db", "transactional_db", "django_db_reset_sequences", "django_db_serialized_rollback", "admin_user", "django_user_model", "django_username_field", "client", "async_client", "admin_client", "rf", "async_rf", "settings", "live_server", "_live_server_helper", "django_assert_num_queries", "django_assert_max_num_queries", "django_capture_on_commit_callbacks", ] @pytest.fixture(scope="session") def django_db_modify_db_settings_tox_suffix() -> None: skip_if_no_django() tox_environment = os.getenv("TOX_PARALLEL_ENV") if tox_environment: # Put a suffix like _py27-django21 on tox workers _set_suffix_to_test_databases(suffix=tox_environment) @pytest.fixture(scope="session") def django_db_modify_db_settings_xdist_suffix(request) -> None: skip_if_no_django() xdist_suffix = getattr(request.config, "workerinput", {}).get("workerid") if xdist_suffix: # Put a suffix like _gw0, _gw1 etc on xdist processes _set_suffix_to_test_databases(suffix=xdist_suffix) @pytest.fixture(scope="session") def django_db_modify_db_settings_parallel_suffix( django_db_modify_db_settings_tox_suffix: None, django_db_modify_db_settings_xdist_suffix: None, ) -> None: skip_if_no_django() @pytest.fixture(scope="session") def django_db_modify_db_settings( django_db_modify_db_settings_parallel_suffix: None, ) -> None: skip_if_no_django() @pytest.fixture(scope="session") def django_db_use_migrations(request) -> bool: return not request.config.getvalue("nomigrations") @pytest.fixture(scope="session") def django_db_keepdb(request) -> bool: return request.config.getvalue("reuse_db") @pytest.fixture(scope="session") def django_db_createdb(request) -> bool: return request.config.getvalue("create_db") @pytest.fixture(scope="session") def django_db_setup( request, django_test_environment: None, django_db_blocker, django_db_use_migrations: bool, django_db_keepdb: bool, django_db_createdb: bool, django_db_modify_db_settings: None, ) -> None: """Top level fixture to ensure test databases are available""" from django.test.utils import setup_databases, teardown_databases setup_databases_args = {} if not django_db_use_migrations: _disable_migrations() if django_db_keepdb and not django_db_createdb: setup_databases_args["keepdb"] = True with django_db_blocker.unblock(): db_cfg = setup_databases( verbosity=request.config.option.verbose, interactive=False, **setup_databases_args ) def teardown_database() -> None: with django_db_blocker.unblock(): try: teardown_databases(db_cfg, verbosity=request.config.option.verbose) except Exception as exc: request.node.warn( pytest.PytestWarning( "Error when trying to teardown test databases: %r" % exc ) ) if not django_db_keepdb: request.addfinalizer(teardown_database) @pytest.fixture() def _django_db_helper( request, django_db_setup: None, django_db_blocker, ) -> None: from django import VERSION if is_django_unittest(request): return marker = request.node.get_closest_marker("django_db") if marker: ( transactional, reset_sequences, databases, serialized_rollback, ) = validate_django_db(marker) else: ( transactional, reset_sequences, databases, serialized_rollback, ) = False, False, None, False transactional = transactional or reset_sequences or ( "transactional_db" in request.fixturenames or "live_server" in request.fixturenames ) reset_sequences = reset_sequences or ( "django_db_reset_sequences" in request.fixturenames ) serialized_rollback = serialized_rollback or ( "django_db_serialized_rollback" in request.fixturenames ) django_db_blocker.unblock() request.addfinalizer(django_db_blocker.restore) import django.db import django.test if transactional: test_case_class = django.test.TransactionTestCase else: test_case_class = django.test.TestCase _reset_sequences = reset_sequences _serialized_rollback = serialized_rollback _databases = databases class PytestDjangoTestCase(test_case_class): # type: ignore[misc,valid-type] reset_sequences = _reset_sequences serialized_rollback = _serialized_rollback if _databases is not None: databases = _databases # For non-transactional tests, skip executing `django.test.TestCase`'s # `setUpClass`/`tearDownClass`, only execute the super class ones. # # `TestCase`'s class setup manages the `setUpTestData`/class-level # transaction functionality. We don't use it; instead we (will) offer # our own alternatives. So it only adds overhead, and does some things # which conflict with our (planned) functionality, particularly, it # closes all database connections in `tearDownClass` which inhibits # wrapping tests in higher-scoped transactions. # # It's possible a new version of Django will add some unrelated # functionality to these methods, in which case skipping them completely # would not be desirable. Let's cross that bridge when we get there... if not transactional: @classmethod def setUpClass(cls) -> None: super(django.test.TestCase, cls).setUpClass() if (3, 2) <= VERSION < (4, 1): django.db.transaction.Atomic._ensure_durability = False @classmethod def tearDownClass(cls) -> None: if (3, 2) <= VERSION < (4, 1): django.db.transaction.Atomic._ensure_durability = True super(django.test.TestCase, cls).tearDownClass() PytestDjangoTestCase.setUpClass() if VERSION >= (4, 0): request.addfinalizer(PytestDjangoTestCase.doClassCleanups) request.addfinalizer(PytestDjangoTestCase.tearDownClass) test_case = PytestDjangoTestCase(methodName="__init__") test_case._pre_setup() request.addfinalizer(test_case._post_teardown) def validate_django_db(marker) -> "_DjangoDb": """Validate the django_db marker. It checks the signature and creates the ``transaction``, ``reset_sequences``, ``databases`` and ``serialized_rollback`` attributes on the marker which will have the correct values. Sequence reset and serialized_rollback are only allowed when combined with transaction. """ def apifun( transaction: bool = False, reset_sequences: bool = False, databases: "_DjangoDbDatabases" = None, serialized_rollback: bool = False, ) -> "_DjangoDb": return transaction, reset_sequences, databases, serialized_rollback return apifun(*marker.args, **marker.kwargs) def _disable_migrations() -> None: from django.conf import settings from django.core.management.commands import migrate class DisableMigrations: def __contains__(self, item: str) -> bool: return True def __getitem__(self, item: str) -> None: return None settings.MIGRATION_MODULES = DisableMigrations() class MigrateSilentCommand(migrate.Command): def handle(self, *args, **kwargs): kwargs["verbosity"] = 0 return super().handle(*args, **kwargs) migrate.Command = MigrateSilentCommand def _set_suffix_to_test_databases(suffix: str) -> None: from django.conf import settings for db_settings in settings.DATABASES.values(): test_name = db_settings.get("TEST", {}).get("NAME") if not test_name: if db_settings["ENGINE"] == "django.db.backends.sqlite3": continue test_name = "test_{}".format(db_settings["NAME"]) if test_name == ":memory:": continue db_settings.setdefault("TEST", {}) db_settings["TEST"]["NAME"] = "{}_{}".format(test_name, suffix) # ############### User visible fixtures ################ @pytest.fixture(scope="function") def db(_django_db_helper: None) -> None: """Require a django test database. This database will be setup with the default fixtures and will have the transaction management disabled. At the end of the test the outer transaction that wraps the test itself will be rolled back to undo any changes to the database (in case the backend supports transactions). This is more limited than the ``transactional_db`` fixture but faster. If both ``db`` and ``transactional_db`` are requested, ``transactional_db`` takes precedence. """ # The `_django_db_helper` fixture checks if `db` is requested. @pytest.fixture(scope="function") def transactional_db(_django_db_helper: None) -> None: """Require a django test database with transaction support. This will re-initialise the django database for each test and is thus slower than the normal ``db`` fixture. If you want to use the database with transactions you must request this resource. If both ``db`` and ``transactional_db`` are requested, ``transactional_db`` takes precedence. """ # The `_django_db_helper` fixture checks if `transactional_db` is requested. @pytest.fixture(scope="function") def django_db_reset_sequences( _django_db_helper: None, transactional_db: None, ) -> None: """Require a transactional test database with sequence reset support. This requests the ``transactional_db`` fixture, and additionally enforces a reset of all auto increment sequences. If the enquiring test relies on such values (e.g. ids as primary keys), you should request this resource to ensure they are consistent across tests. """ # The `_django_db_helper` fixture checks if `django_db_reset_sequences` # is requested. @pytest.fixture(scope="function") def django_db_serialized_rollback( _django_db_helper: None, db: None, ) -> None: """Require a test database with serialized rollbacks. This requests the ``db`` fixture, and additionally performs rollback emulation - serializes the database contents during setup and restores it during teardown. This fixture may be useful for transactional tests, so is usually combined with ``transactional_db``, but can also be useful on databases which do not support transactions. Note that this will slow down that test suite by approximately 3x. """ # The `_django_db_helper` fixture checks if `django_db_serialized_rollback` # is requested. @pytest.fixture() def client() -> "django.test.client.Client": """A Django test client instance.""" skip_if_no_django() from django.test.client import Client return Client() @pytest.fixture() def async_client() -> "django.test.client.AsyncClient": """A Django test async client instance.""" skip_if_no_django() from django.test.client import AsyncClient return AsyncClient() @pytest.fixture() def django_user_model(db: None): """The class of Django's user model.""" from django.contrib.auth import get_user_model return get_user_model() @pytest.fixture() def django_username_field(django_user_model) -> str: """The fieldname for the username used with Django's user model.""" return django_user_model.USERNAME_FIELD @pytest.fixture() def admin_user( db: None, django_user_model, django_username_field: str, ): """A Django admin user. This uses an existing user with username "admin", or creates a new one with password "password". """ UserModel = django_user_model username_field = django_username_field username = "admin@example.com" if username_field == "email" else "admin" try: # The default behavior of `get_by_natural_key()` is to look up by `username_field`. # However the user model is free to override it with any sort of custom behavior. # The Django authentication backend already assumes the lookup is by username, # so we can assume so as well. user = UserModel._default_manager.get_by_natural_key(username) except UserModel.DoesNotExist: user_data = {} if "email" in UserModel.REQUIRED_FIELDS: user_data["email"] = "admin@example.com" user_data["password"] = "password" user_data[username_field] = username user = UserModel._default_manager.create_superuser(**user_data) return user @pytest.fixture() def admin_client( db: None, admin_user, ) -> "django.test.client.Client": """A Django test client logged in as an admin user.""" from django.test.client import Client client = Client() client.force_login(admin_user) return client @pytest.fixture() def rf() -> "django.test.client.RequestFactory": """RequestFactory instance""" skip_if_no_django() from django.test.client import RequestFactory return RequestFactory() @pytest.fixture() def async_rf() -> "django.test.client.AsyncRequestFactory": """AsyncRequestFactory instance""" skip_if_no_django() from django.test.client import AsyncRequestFactory return AsyncRequestFactory() class SettingsWrapper: _to_restore = [] # type: List[Any] def __delattr__(self, attr: str) -> None: from django.test import override_settings override = override_settings() override.enable() from django.conf import settings delattr(settings, attr) self._to_restore.append(override) def __setattr__(self, attr: str, value) -> None: from django.test import override_settings override = override_settings(**{attr: value}) override.enable() self._to_restore.append(override) def __getattr__(self, attr: str): from django.conf import settings return getattr(settings, attr) def finalize(self) -> None: for override in reversed(self._to_restore): override.disable() del self._to_restore[:] @pytest.fixture() def settings(): """A Django settings object which restores changes after the testrun""" skip_if_no_django() wrapper = SettingsWrapper() yield wrapper wrapper.finalize() @pytest.fixture(scope="session") def live_server(request): """Run a live Django server in the background during tests The address the server is started from is taken from the --liveserver command line option or if this is not provided from the DJANGO_LIVE_TEST_SERVER_ADDRESS environment variable. If neither is provided ``localhost`` is used. See the Django documentation for its full syntax. NOTE: If the live server needs database access to handle a request your test will have to request database access. Furthermore when the tests want to see data added by the live-server (or the other way around) transactional database access will be needed as data inside a transaction is not shared between the live server and test code. Static assets will be automatically served when ``django.contrib.staticfiles`` is available in INSTALLED_APPS. """ skip_if_no_django() addr = request.config.getvalue("liveserver") or os.getenv( "DJANGO_LIVE_TEST_SERVER_ADDRESS" ) or "localhost" server = live_server_helper.LiveServer(addr) request.addfinalizer(server.stop) return server @pytest.fixture(autouse=True, scope="function") def _live_server_helper(request) -> None: """Helper to make live_server work, internal to pytest-django. This helper will dynamically request the transactional_db fixture for a test which uses the live_server fixture. This allows the server and test to access the database without having to mark this explicitly which is handy since it is usually required and matches the Django behaviour. The separate helper is required since live_server can not request transactional_db directly since it is session scoped instead of function-scoped. It will also override settings only for the duration of the test. """ if "live_server" not in request.fixturenames: return request.getfixturevalue("transactional_db") live_server = request.getfixturevalue("live_server") live_server._live_server_modified_settings.enable() request.addfinalizer(live_server._live_server_modified_settings.disable) @contextmanager def _assert_num_queries( config, num: int, exact: bool = True, connection=None, info=None, ) -> Generator["django.test.utils.CaptureQueriesContext", None, None]: from django.test.utils import CaptureQueriesContext if connection is None: from django.db import connection as conn else: conn = connection verbose = config.getoption("verbose") > 0 with CaptureQueriesContext(conn) as context: yield context num_performed = len(context) if exact: failed = num != num_performed else: failed = num_performed > num if failed: msg = "Expected to perform {} queries {}{}".format( num, "" if exact else "or less ", "but {} done".format( num_performed == 1 and "1 was" or "{} were".format(num_performed) ), ) if info: msg += "\n{}".format(info) if verbose: sqls = (q["sql"] for q in context.captured_queries) msg += "\n\nQueries:\n========\n\n" + "\n\n".join(sqls) else: msg += " (add -v option to show queries)" pytest.fail(msg) @pytest.fixture(scope="function") def django_assert_num_queries(pytestconfig): return partial(_assert_num_queries, pytestconfig) @pytest.fixture(scope="function") def django_assert_max_num_queries(pytestconfig): return partial(_assert_num_queries, pytestconfig, exact=False) @contextmanager def _capture_on_commit_callbacks( *, using: Optional[str] = None, execute: bool = False ): from django.db import DEFAULT_DB_ALIAS, connections from django.test import TestCase if using is None: using = DEFAULT_DB_ALIAS # Polyfill of Django code as of Django 3.2. if get_django_version() < (3, 2): callbacks = [] # type: List[Callable[[], Any]] start_count = len(connections[using].run_on_commit) try: yield callbacks finally: run_on_commit = connections[using].run_on_commit[start_count:] callbacks[:] = [func for sids, func in run_on_commit] if execute: for callback in callbacks: callback() else: with TestCase.captureOnCommitCallbacks(using=using, execute=execute) as callbacks: yield callbacks @pytest.fixture(scope="function") def django_capture_on_commit_callbacks(): return _capture_on_commit_callbacks pytest-django-4.5.2/pytest_django/lazy_django.py000066400000000000000000000017211415366732400220530ustar00rootroot00000000000000""" Helpers to load Django lazily when Django settings can't be configured. """ import os import sys from typing import Any, Tuple import pytest def skip_if_no_django() -> None: """Raises a skip exception when no Django settings are available""" if not django_settings_is_configured(): pytest.skip("no Django settings") def django_settings_is_configured() -> bool: """Return whether the Django settings module has been configured. This uses either the DJANGO_SETTINGS_MODULE environment variable, or the configured flag in the Django settings object if django.conf has already been imported. """ ret = bool(os.environ.get("DJANGO_SETTINGS_MODULE")) if not ret and "django.conf" in sys.modules: django_conf = sys.modules["django.conf"] # type: Any return django_conf.settings.configured return ret def get_django_version() -> Tuple[int, int, int, str, int]: import django return django.VERSION pytest-django-4.5.2/pytest_django/live_server_helper.py000066400000000000000000000051731415366732400234430ustar00rootroot00000000000000from typing import Any, Dict class LiveServer: """The liveserver fixture This is the object that the ``live_server`` fixture returns. The ``live_server`` fixture handles creation and stopping. """ def __init__(self, addr: str) -> None: from django.db import connections from django.test.testcases import LiveServerThread from django.test.utils import modify_settings liveserver_kwargs = {} # type: Dict[str, Any] connections_override = {} for conn in connections.all(): # If using in-memory sqlite databases, pass the connections to # the server thread. if conn.vendor == "sqlite" and conn.is_in_memory_db(): # Explicitly enable thread-shareability for this connection. conn.inc_thread_sharing() connections_override[conn.alias] = conn liveserver_kwargs["connections_override"] = connections_override from django.conf import settings if "django.contrib.staticfiles" in settings.INSTALLED_APPS: from django.contrib.staticfiles.handlers import StaticFilesHandler liveserver_kwargs["static_handler"] = StaticFilesHandler else: from django.test.testcases import _StaticFilesHandler liveserver_kwargs["static_handler"] = _StaticFilesHandler try: host, port = addr.split(":") except ValueError: host = addr else: liveserver_kwargs["port"] = int(port) self.thread = LiveServerThread(host, **liveserver_kwargs) self._live_server_modified_settings = modify_settings( ALLOWED_HOSTS={"append": host} ) # `_live_server_modified_settings` is enabled and disabled by # `_live_server_helper`. self.thread.daemon = True self.thread.start() self.thread.is_ready.wait() if self.thread.error: error = self.thread.error self.stop() raise error def stop(self) -> None: """Stop the server""" # Terminate the live server's thread. self.thread.terminate() # Restore shared connections' non-shareability. for conn in self.thread.connections_override.values(): conn.dec_thread_sharing() @property def url(self) -> str: return "http://{}:{}".format(self.thread.host, self.thread.port) def __str__(self) -> str: return self.url def __add__(self, other) -> str: return "{}{}".format(self, other) def __repr__(self) -> str: return "" % self.url pytest-django-4.5.2/pytest_django/plugin.py000066400000000000000000000563241415366732400210610ustar00rootroot00000000000000"""A pytest plugin which helps testing Django applications This plugin handles creating and destroying the test environment and test database and provides some useful text fixtures. """ import contextlib import inspect import os import pathlib import sys from functools import reduce from typing import Generator, List, Optional, Tuple, Union import pytest from .django_compat import is_django_unittest # noqa from .fixtures import _django_db_helper # noqa from .fixtures import _live_server_helper # noqa from .fixtures import admin_client # noqa from .fixtures import admin_user # noqa from .fixtures import async_client # noqa from .fixtures import async_rf # noqa from .fixtures import client # noqa from .fixtures import db # noqa from .fixtures import django_assert_max_num_queries # noqa from .fixtures import django_assert_num_queries # noqa from .fixtures import django_capture_on_commit_callbacks # noqa from .fixtures import django_db_createdb # noqa from .fixtures import django_db_keepdb # noqa from .fixtures import django_db_modify_db_settings # noqa from .fixtures import django_db_modify_db_settings_parallel_suffix # noqa from .fixtures import django_db_modify_db_settings_tox_suffix # noqa from .fixtures import django_db_modify_db_settings_xdist_suffix # noqa from .fixtures import django_db_reset_sequences # noqa from .fixtures import django_db_serialized_rollback # noqa from .fixtures import django_db_setup # noqa from .fixtures import django_db_use_migrations # noqa from .fixtures import django_user_model # noqa from .fixtures import django_username_field # noqa from .fixtures import live_server # noqa from .fixtures import rf # noqa from .fixtures import settings # noqa from .fixtures import transactional_db # noqa from .fixtures import validate_django_db from .lazy_django import django_settings_is_configured, skip_if_no_django TYPE_CHECKING = False if TYPE_CHECKING: from typing import ContextManager, NoReturn import django SETTINGS_MODULE_ENV = "DJANGO_SETTINGS_MODULE" CONFIGURATION_ENV = "DJANGO_CONFIGURATION" INVALID_TEMPLATE_VARS_ENV = "FAIL_INVALID_TEMPLATE_VARS" _report_header = [] # ############### pytest hooks ################ @pytest.hookimpl() def pytest_addoption(parser) -> None: group = parser.getgroup("django") group.addoption( "--reuse-db", action="store_true", dest="reuse_db", default=False, help="Re-use the testing database if it already exists, " "and do not remove it when the test finishes.", ) group.addoption( "--create-db", action="store_true", dest="create_db", default=False, help="Re-create the database, even if it exists. This " "option can be used to override --reuse-db.", ) group.addoption( "--ds", action="store", type=str, dest="ds", default=None, help="Set DJANGO_SETTINGS_MODULE.", ) group.addoption( "--dc", action="store", type=str, dest="dc", default=None, help="Set DJANGO_CONFIGURATION.", ) group.addoption( "--nomigrations", "--no-migrations", action="store_true", dest="nomigrations", default=False, help="Disable Django migrations on test setup", ) group.addoption( "--migrations", action="store_false", dest="nomigrations", default=False, help="Enable Django migrations on test setup", ) parser.addini( CONFIGURATION_ENV, "django-configurations class to use by pytest-django." ) group.addoption( "--liveserver", default=None, help="Address and port for the live_server fixture.", ) parser.addini( SETTINGS_MODULE_ENV, "Django settings module to use by pytest-django." ) parser.addini( "django_find_project", "Automatically find and add a Django project to the " "Python path.", type="bool", default=True, ) parser.addini( "django_debug_mode", "How to set the Django DEBUG setting (default `False`). " "Use `keep` to not override.", default="False", ) group.addoption( "--fail-on-template-vars", action="store_true", dest="itv", default=False, help="Fail for invalid variables in templates.", ) parser.addini( INVALID_TEMPLATE_VARS_ENV, "Fail for invalid variables in templates.", type="bool", default=False, ) PROJECT_FOUND = ( "pytest-django found a Django project in %s " "(it contains manage.py) and added it to the Python path.\n" 'If this is wrong, add "django_find_project = false" to ' "pytest.ini and explicitly manage your Python path." ) PROJECT_NOT_FOUND = ( "pytest-django could not find a Django project " "(no manage.py file could be found). You must " "explicitly add your Django project to the Python path " "to have it picked up." ) PROJECT_SCAN_DISABLED = ( "pytest-django did not search for Django " "projects since it is disabled in the configuration " '("django_find_project = false")' ) @contextlib.contextmanager def _handle_import_error(extra_message: str) -> Generator[None, None, None]: try: yield except ImportError as e: django_msg = (e.args[0] + "\n\n") if e.args else "" msg = django_msg + extra_message raise ImportError(msg) def _add_django_project_to_path(args) -> str: def is_django_project(path: pathlib.Path) -> bool: try: return path.is_dir() and (path / "manage.py").exists() except OSError: return False def arg_to_path(arg: str) -> pathlib.Path: # Test classes or functions can be appended to paths separated by :: arg = arg.split("::", 1)[0] return pathlib.Path(arg) def find_django_path(args) -> Optional[pathlib.Path]: str_args = (str(arg) for arg in args) path_args = [arg_to_path(x) for x in str_args if not x.startswith("-")] cwd = pathlib.Path.cwd() if not path_args: path_args.append(cwd) elif cwd not in path_args: path_args.append(cwd) for arg in path_args: if is_django_project(arg): return arg for parent in arg.parents: if is_django_project(parent): return parent return None project_dir = find_django_path(args) if project_dir: sys.path.insert(0, str(project_dir.absolute())) return PROJECT_FOUND % project_dir return PROJECT_NOT_FOUND def _setup_django() -> None: if "django" not in sys.modules: return import django.conf # Avoid force-loading Django when settings are not properly configured. if not django.conf.settings.configured: return import django.apps if not django.apps.apps.ready: django.setup() _blocking_manager.block() def _get_boolean_value( x: Union[None, bool, str], name: str, default: Optional[bool] = None, ) -> bool: if x is None: return bool(default) if isinstance(x, bool): return x possible_values = {"true": True, "false": False, "1": True, "0": False} try: return possible_values[x.lower()] except KeyError: raise ValueError( "{} is not a valid value for {}. " "It must be one of {}.".format(x, name, ", ".join(possible_values.keys())) ) @pytest.hookimpl() def pytest_load_initial_conftests( early_config, parser, args: List[str], ) -> None: # Register the marks early_config.addinivalue_line( "markers", "django_db(transaction=False, reset_sequences=False, databases=None, " "serialized_rollback=False): " "Mark the test as using the Django test database. " "The *transaction* argument allows you to use real transactions " "in the test like Django's TransactionTestCase. " "The *reset_sequences* argument resets database sequences before " "the test. " "The *databases* argument sets which database aliases the test " "uses (by default, only 'default'). Use '__all__' for all databases. " "The *serialized_rollback* argument enables rollback emulation for " "the test.", ) early_config.addinivalue_line( "markers", "urls(modstr): Use a different URLconf for this test, similar to " "the `urls` attribute of Django's `TestCase` objects. *modstr* is " "a string specifying the module of a URL config, e.g. " '"my_app.test_urls".', ) early_config.addinivalue_line( "markers", "ignore_template_errors(): ignore errors from invalid template " "variables (if --fail-on-template-vars is used).", ) options = parser.parse_known_args(args) if options.version or options.help: return django_find_project = _get_boolean_value( early_config.getini("django_find_project"), "django_find_project" ) if django_find_project: _django_project_scan_outcome = _add_django_project_to_path(args) else: _django_project_scan_outcome = PROJECT_SCAN_DISABLED if ( options.itv or _get_boolean_value( os.environ.get(INVALID_TEMPLATE_VARS_ENV), INVALID_TEMPLATE_VARS_ENV ) or early_config.getini(INVALID_TEMPLATE_VARS_ENV) ): os.environ[INVALID_TEMPLATE_VARS_ENV] = "true" def _get_option_with_source( option: Optional[str], envname: str, ) -> Union[Tuple[str, str], Tuple[None, None]]: if option: return option, "option" if envname in os.environ: return os.environ[envname], "env" cfgval = early_config.getini(envname) if cfgval: return cfgval, "ini" return None, None ds, ds_source = _get_option_with_source(options.ds, SETTINGS_MODULE_ENV) dc, dc_source = _get_option_with_source(options.dc, CONFIGURATION_ENV) if ds: _report_header.append("settings: {} (from {})".format(ds, ds_source)) os.environ[SETTINGS_MODULE_ENV] = ds if dc: _report_header.append("configuration: {} (from {})".format(dc, dc_source)) os.environ[CONFIGURATION_ENV] = dc # Install the django-configurations importer import configurations.importer configurations.importer.install() # Forcefully load Django settings, throws ImportError or # ImproperlyConfigured if settings cannot be loaded. from django.conf import settings as dj_settings with _handle_import_error(_django_project_scan_outcome): dj_settings.DATABASES _setup_django() @pytest.hookimpl() def pytest_report_header() -> Optional[List[str]]: if _report_header: return ["django: " + ", ".join(_report_header)] return None @pytest.hookimpl(trylast=True) def pytest_configure() -> None: # Allow Django settings to be configured in a user pytest_configure call, # but make sure we call django.setup() _setup_django() @pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(items: List[pytest.Item]) -> None: # If Django is not configured we don't need to bother if not django_settings_is_configured(): return from django.test import TestCase, TransactionTestCase def get_order_number(test: pytest.Item) -> int: test_cls = getattr(test, "cls", None) if test_cls and issubclass(test_cls, TransactionTestCase): # Note, TestCase is a subclass of TransactionTestCase. uses_db = True transactional = not issubclass(test_cls, TestCase) else: marker_db = test.get_closest_marker("django_db") if marker_db: ( transaction, reset_sequences, databases, serialized_rollback, ) = validate_django_db(marker_db) uses_db = True transactional = transaction or reset_sequences else: uses_db = False transactional = False fixtures = getattr(test, "fixturenames", []) transactional = transactional or "transactional_db" in fixtures uses_db = uses_db or "db" in fixtures if transactional: return 1 elif uses_db: return 0 else: return 2 items.sort(key=get_order_number) @pytest.fixture(autouse=True, scope="session") def django_test_environment(request) -> None: """ Ensure that Django is loaded and has its testing environment setup. XXX It is a little dodgy that this is an autouse fixture. Perhaps an email fixture should be requested in order to be able to use the Django email machinery just like you need to request a db fixture for access to the Django database, etc. But without duplicating a lot more of Django's test support code we need to follow this model. """ if django_settings_is_configured(): _setup_django() from django.test.utils import ( setup_test_environment, teardown_test_environment, ) debug_ini = request.config.getini("django_debug_mode") if debug_ini == "keep": debug = None else: debug = _get_boolean_value(debug_ini, "django_debug_mode", False) setup_test_environment(debug=debug) request.addfinalizer(teardown_test_environment) @pytest.fixture(scope="session") def django_db_blocker() -> "Optional[_DatabaseBlocker]": """Wrapper around Django's database access. This object can be used to re-enable database access. This fixture is used internally in pytest-django to build the other fixtures and can be used for special database handling. The object is a context manager and provides the methods .unblock()/.block() and .restore() to temporarily enable database access. This is an advanced feature that is meant to be used to implement database fixtures. """ if not django_settings_is_configured(): return None return _blocking_manager @pytest.fixture(autouse=True) def _django_db_marker(request) -> None: """Implement the django_db marker, internal to pytest-django.""" marker = request.node.get_closest_marker("django_db") if marker: request.getfixturevalue("_django_db_helper") @pytest.fixture(autouse=True, scope="class") def _django_setup_unittest( request, django_db_blocker: "_DatabaseBlocker", ) -> Generator[None, None, None]: """Setup a django unittest, internal to pytest-django.""" if not django_settings_is_configured() or not is_django_unittest(request): yield return # Fix/patch pytest. # Before pytest 5.4: https://github.com/pytest-dev/pytest/issues/5991 # After pytest 5.4: https://github.com/pytest-dev/pytest-django/issues/824 from _pytest.unittest import TestCaseFunction original_runtest = TestCaseFunction.runtest def non_debugging_runtest(self) -> None: self._testcase(result=self) try: TestCaseFunction.runtest = non_debugging_runtest # type: ignore[assignment] request.getfixturevalue("django_db_setup") with django_db_blocker.unblock(): yield finally: TestCaseFunction.runtest = original_runtest # type: ignore[assignment] @pytest.fixture(scope="function", autouse=True) def _dj_autoclear_mailbox() -> None: if not django_settings_is_configured(): return from django.core import mail del mail.outbox[:] @pytest.fixture(scope="function") def mailoutbox( django_mail_patch_dns: None, _dj_autoclear_mailbox: None, ) -> "Optional[List[django.core.mail.EmailMessage]]": if not django_settings_is_configured(): return None from django.core import mail return mail.outbox @pytest.fixture(scope="function") def django_mail_patch_dns( monkeypatch, django_mail_dnsname: str, ) -> None: from django.core import mail monkeypatch.setattr(mail.message, "DNS_NAME", django_mail_dnsname) @pytest.fixture(scope="function") def django_mail_dnsname() -> str: return "fake-tests.example.com" @pytest.fixture(autouse=True, scope="function") def _django_set_urlconf(request) -> None: """Apply the @pytest.mark.urls marker, internal to pytest-django.""" marker = request.node.get_closest_marker("urls") if marker: skip_if_no_django() import django.conf from django.urls import clear_url_caches, set_urlconf urls = validate_urls(marker) original_urlconf = django.conf.settings.ROOT_URLCONF django.conf.settings.ROOT_URLCONF = urls clear_url_caches() set_urlconf(None) def restore() -> None: django.conf.settings.ROOT_URLCONF = original_urlconf # Copy the pattern from # https://github.com/django/django/blob/main/django/test/signals.py#L152 clear_url_caches() set_urlconf(None) request.addfinalizer(restore) @pytest.fixture(autouse=True, scope="session") def _fail_for_invalid_template_variable(): """Fixture that fails for invalid variables in templates. This fixture will fail each test that uses django template rendering should a template contain an invalid template variable. The fail message will include the name of the invalid variable and in most cases the template name. It does not raise an exception, but fails, as the stack trace doesn't offer any helpful information to debug. This behavior can be switched off using the marker: ``pytest.mark.ignore_template_errors`` """ class InvalidVarException: """Custom handler for invalid strings in templates.""" def __init__(self) -> None: self.fail = True def __contains__(self, key: str) -> bool: return key == "%s" @staticmethod def _get_origin(): stack = inspect.stack() # Try to use topmost `self.origin` first (Django 1.9+, and with # TEMPLATE_DEBUG).. for f in stack[2:]: func = f[3] if func == "render": frame = f[0] try: origin = frame.f_locals["self"].origin except (AttributeError, KeyError): continue if origin is not None: return origin from django.template import Template # finding the ``render`` needle in the stack frameinfo = reduce( lambda x, y: y[3] == "render" and "base.py" in y[1] and y or x, stack ) # assert 0, stack frame = frameinfo[0] # finding only the frame locals in all frame members f_locals = reduce( lambda x, y: y[0] == "f_locals" and y or x, inspect.getmembers(frame) )[1] # ``django.template.base.Template`` template = f_locals["self"] if isinstance(template, Template): return template.name def __mod__(self, var: str) -> str: origin = self._get_origin() if origin: msg = "Undefined template variable '{}' in '{}'".format(var, origin) else: msg = "Undefined template variable '%s'" % var if self.fail: pytest.fail(msg) else: return msg if ( os.environ.get(INVALID_TEMPLATE_VARS_ENV, "false") == "true" and django_settings_is_configured() ): from django.conf import settings as dj_settings if dj_settings.TEMPLATES: dj_settings.TEMPLATES[0]["OPTIONS"]["string_if_invalid"] = InvalidVarException() @pytest.fixture(autouse=True) def _template_string_if_invalid_marker(request) -> None: """Apply the @pytest.mark.ignore_template_errors marker, internal to pytest-django.""" marker = request.keywords.get("ignore_template_errors", None) if os.environ.get(INVALID_TEMPLATE_VARS_ENV, "false") == "true": if marker and django_settings_is_configured(): from django.conf import settings as dj_settings if dj_settings.TEMPLATES: dj_settings.TEMPLATES[0]["OPTIONS"]["string_if_invalid"].fail = False @pytest.fixture(autouse=True, scope="function") def _django_clear_site_cache() -> None: """Clears ``django.contrib.sites.models.SITE_CACHE`` to avoid unexpected behavior with cached site objects. """ if django_settings_is_configured(): from django.conf import settings as dj_settings if "django.contrib.sites" in dj_settings.INSTALLED_APPS: from django.contrib.sites.models import Site Site.objects.clear_cache() # ############### Helper Functions ################ class _DatabaseBlockerContextManager: def __init__(self, db_blocker) -> None: self._db_blocker = db_blocker def __enter__(self) -> None: pass def __exit__(self, exc_type, exc_value, traceback) -> None: self._db_blocker.restore() class _DatabaseBlocker: """Manager for django.db.backends.base.base.BaseDatabaseWrapper. This is the object returned by django_db_blocker. """ def __init__(self): self._history = [] self._real_ensure_connection = None @property def _dj_db_wrapper(self) -> "django.db.backends.base.base.BaseDatabaseWrapper": from django.db.backends.base.base import BaseDatabaseWrapper # The first time the _dj_db_wrapper is accessed, we will save a # reference to the real implementation. if self._real_ensure_connection is None: self._real_ensure_connection = BaseDatabaseWrapper.ensure_connection return BaseDatabaseWrapper def _save_active_wrapper(self) -> None: self._history.append(self._dj_db_wrapper.ensure_connection) def _blocking_wrapper(*args, **kwargs) -> "NoReturn": __tracebackhide__ = True __tracebackhide__ # Silence pyflakes raise RuntimeError( "Database access not allowed, " 'use the "django_db" mark, or the ' '"db" or "transactional_db" fixtures to enable it.' ) def unblock(self) -> "ContextManager[None]": """Enable access to the Django database.""" self._save_active_wrapper() self._dj_db_wrapper.ensure_connection = self._real_ensure_connection return _DatabaseBlockerContextManager(self) def block(self) -> "ContextManager[None]": """Disable access to the Django database.""" self._save_active_wrapper() self._dj_db_wrapper.ensure_connection = self._blocking_wrapper return _DatabaseBlockerContextManager(self) def restore(self) -> None: self._dj_db_wrapper.ensure_connection = self._history.pop() _blocking_manager = _DatabaseBlocker() def validate_urls(marker) -> List[str]: """Validate the urls marker. It checks the signature and creates the `urls` attribute on the marker which will have the correct value. """ def apifun(urls: List[str]) -> List[str]: return urls return apifun(*marker.args, **marker.kwargs) pytest-django-4.5.2/pytest_django/py.typed000066400000000000000000000000001415366732400206640ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/000077500000000000000000000000001415366732400202365ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/__init__.py000066400000000000000000000000001415366732400223350ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/app/000077500000000000000000000000001415366732400210165ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/app/__init__.py000066400000000000000000000000001415366732400231150ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/app/fixtures/000077500000000000000000000000001415366732400226675ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/app/fixtures/items.json000066400000000000000000000001441415366732400247020ustar00rootroot00000000000000[ { "pk": 1, "model": "app.item", "fields": { "name": "Fixture item" } } ]pytest-django-4.5.2/pytest_django_test/app/migrations/000077500000000000000000000000001415366732400231725ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/app/migrations/0001_initial.py000066400000000000000000000021561415366732400256410ustar00rootroot00000000000000from typing import List, Tuple from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [] # type: List[Tuple[str, str]] operations = [ migrations.CreateModel( name="Item", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=100)), ], ), migrations.CreateModel( name="SecondItem", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=100)), ], ), ] pytest-django-4.5.2/pytest_django_test/app/migrations/__init__.py000066400000000000000000000000001415366732400252710ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/app/models.py000066400000000000000000000004111415366732400226470ustar00rootroot00000000000000from django.db import models # Routed to database "main". class Item(models.Model): name = models.CharField(max_length=100) # type: str # Routed to database "second". class SecondItem(models.Model): name = models.CharField(max_length=100) # type: str pytest-django-4.5.2/pytest_django_test/app/static/000077500000000000000000000000001415366732400223055ustar00rootroot00000000000000pytest-django-4.5.2/pytest_django_test/app/static/a_file.txt000066400000000000000000000000041415366732400242570ustar00rootroot00000000000000bla pytest-django-4.5.2/pytest_django_test/app/views.py000066400000000000000000000007001415366732400225220ustar00rootroot00000000000000from django.http import HttpRequest, HttpResponse from django.template import Template from django.template.context import Context from .models import Item def admin_required_view(request: HttpRequest) -> HttpResponse: assert request.user.is_staff return HttpResponse(Template("You are an admin").render(Context())) def item_count(request: HttpRequest) -> HttpResponse: return HttpResponse("Item count: %d" % Item.objects.count()) pytest-django-4.5.2/pytest_django_test/db_helpers.py000066400000000000000000000127251415366732400227260ustar00rootroot00000000000000import os import sqlite3 import subprocess import pytest from django.conf import settings from django.utils.encoding import force_str # Construct names for the "inner" database used in runpytest tests _settings = settings.DATABASES["default"] DB_NAME = _settings["NAME"] TEST_DB_NAME = _settings["TEST"]["NAME"] if _settings["ENGINE"] == "django.db.backends.sqlite3" and TEST_DB_NAME is None: TEST_DB_NAME = ":memory:" SECOND_DB_NAME = ":memory:" SECOND_TEST_DB_NAME = ":memory:" else: DB_NAME += "_inner" if TEST_DB_NAME is None: # No explicit test db name was given, construct a default one TEST_DB_NAME = "test_{}_inner".format(DB_NAME) else: # An explicit test db name was given, is that as the base name TEST_DB_NAME = "{}_inner".format(TEST_DB_NAME) SECOND_DB_NAME = DB_NAME + '_second' if DB_NAME is not None else None SECOND_TEST_DB_NAME = TEST_DB_NAME + '_second' if DB_NAME is not None else None def get_db_engine(): return _settings["ENGINE"].split(".")[-1] class CmdResult: def __init__(self, status_code, std_out, std_err): self.status_code = status_code self.std_out = std_out self.std_err = std_err def run_cmd(*args, env=None): r = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={**os.environ, **(env or {})}, ) stdoutdata, stderrdata = r.communicate() ret = r.wait() return CmdResult(ret, stdoutdata, stderrdata) def run_psql(*args): env = {} user = _settings.get("USER") if user: # pragma: no branch args = ("-U", user, *args) password = _settings.get("PASSWORD") if password: # pragma: no branch env["PGPASSWORD"] = password host = _settings.get("HOST") if host: # pragma: no branch args = ("-h", host, *args) return run_cmd("psql", *args, env=env) def run_mysql(*args): user = _settings.get("USER") if user: # pragma: no branch args = ("-u", user, *args) password = _settings.get("PASSWORD") if password: # pragma: no branch # Note: "-ppassword" must be a single argument. args = ("-p" + password, *args) host = _settings.get("HOST") if host: # pragma: no branch args = ("-h", host, *args) return run_cmd("mysql", *args) def skip_if_sqlite_in_memory(): if ( _settings["ENGINE"] == "django.db.backends.sqlite3" and _settings["TEST"]["NAME"] is None ): pytest.skip("Do not test db reuse since database does not support it") def _get_db_name(db_suffix=None): name = TEST_DB_NAME if db_suffix: name = "{}_{}".format(name, db_suffix) return name def drop_database(db_suffix=None): name = _get_db_name(db_suffix) db_engine = get_db_engine() if db_engine == "postgresql": r = run_psql("postgres", "-c", "DROP DATABASE %s" % name) assert "DROP DATABASE" in force_str( r.std_out ) or "does not exist" in force_str(r.std_err) return if db_engine == "mysql": r = run_mysql("-e", "DROP DATABASE %s" % name) assert "database doesn't exist" in force_str(r.std_err) or r.status_code == 0 return assert db_engine == "sqlite3", "%s cannot be tested properly!" % db_engine assert name != ":memory:", "sqlite in-memory database cannot be dropped!" if os.path.exists(name): # pragma: no branch os.unlink(name) def db_exists(db_suffix=None): name = _get_db_name(db_suffix) db_engine = get_db_engine() if db_engine == "postgresql": r = run_psql(name, "-c", "SELECT 1") return r.status_code == 0 if db_engine == "mysql": r = run_mysql(name, "-e", "SELECT 1") return r.status_code == 0 assert db_engine == "sqlite3", "%s cannot be tested properly!" % db_engine assert TEST_DB_NAME != ":memory:", ( "sqlite in-memory database cannot be checked for existence!") return os.path.exists(name) def mark_database(): db_engine = get_db_engine() if db_engine == "postgresql": r = run_psql(TEST_DB_NAME, "-c", "CREATE TABLE mark_table();") assert r.status_code == 0 return if db_engine == "mysql": r = run_mysql(TEST_DB_NAME, "-e", "CREATE TABLE mark_table(kaka int);") assert r.status_code == 0 return assert db_engine == "sqlite3", "%s cannot be tested properly!" % db_engine assert TEST_DB_NAME != ":memory:", ( "sqlite in-memory database cannot be marked!") conn = sqlite3.connect(TEST_DB_NAME) try: with conn: conn.execute("CREATE TABLE mark_table(kaka int);") finally: # Close the DB even if an error is raised conn.close() def mark_exists(): db_engine = get_db_engine() if db_engine == "postgresql": r = run_psql(TEST_DB_NAME, "-c", "SELECT 1 FROM mark_table") return r.status_code == 0 if db_engine == "mysql": r = run_mysql(TEST_DB_NAME, "-e", "SELECT 1 FROM mark_table") return r.status_code == 0 assert db_engine == "sqlite3", "%s cannot be tested properly!" % db_engine assert TEST_DB_NAME != ":memory:", ( "sqlite in-memory database cannot be checked for mark!") conn = sqlite3.connect(TEST_DB_NAME) try: with conn: conn.execute("SELECT 1 FROM mark_table") return True except sqlite3.OperationalError: return False finally: # Close the DB even if an error is raised conn.close() pytest-django-4.5.2/pytest_django_test/db_router.py000066400000000000000000000010331415366732400225720ustar00rootroot00000000000000class DbRouter: def db_for_read(self, model, **hints): if model._meta.app_label == 'app' and model._meta.model_name == 'seconditem': return 'second' return None def db_for_write(self, model, **hints): if model._meta.app_label == 'app' and model._meta.model_name == 'seconditem': return 'second' return None def allow_migrate(self, db, app_label, model_name=None, **hints): if app_label == 'app' and model_name == 'seconditem': return db == 'second' pytest-django-4.5.2/pytest_django_test/settings_base.py000066400000000000000000000014451415366732400234460ustar00rootroot00000000000000ROOT_URLCONF = "pytest_django_test.urls" INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.sites", "pytest_django_test.app", ] STATIC_URL = "/static/" SECRET_KEY = "foobar" MIDDLEWARE = [ "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ] TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": {}, } ] DATABASE_ROUTERS = ['pytest_django_test.db_router.DbRouter'] USE_TZ = True pytest-django-4.5.2/pytest_django_test/settings_mysql_innodb.py000066400000000000000000000032431415366732400252300ustar00rootroot00000000000000from os import environ from .settings_base import * # noqa: F401 F403 DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "pytest_django_tests_default", "USER": environ.get("TEST_DB_USER", "root"), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", "localhost"), "OPTIONS": { "init_command": "SET default_storage_engine=InnoDB", "charset": "utf8mb4", }, "TEST": { "CHARSET": "utf8mb4", "COLLATION": "utf8mb4_unicode_ci", }, }, "replica": { "ENGINE": "django.db.backends.mysql", "NAME": "pytest_django_tests_replica", "USER": environ.get("TEST_DB_USER", "root"), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", "localhost"), "OPTIONS": { "init_command": "SET default_storage_engine=InnoDB", "charset": "utf8mb4", }, "TEST": { "MIRROR": "default", "CHARSET": "utf8mb4", "COLLATION": "utf8mb4_unicode_ci", }, }, "second": { "ENGINE": "django.db.backends.mysql", "NAME": "pytest_django_tests_second", "USER": environ.get("TEST_DB_USER", "root"), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", "localhost"), "OPTIONS": { "init_command": "SET default_storage_engine=InnoDB", "charset": "utf8mb4", }, "TEST": { "CHARSET": "utf8mb4", "COLLATION": "utf8mb4_unicode_ci", }, }, } pytest-django-4.5.2/pytest_django_test/settings_mysql_myisam.py000066400000000000000000000032431415366732400252560ustar00rootroot00000000000000from os import environ from .settings_base import * # noqa: F401 F403 DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "pytest_django_tests_default", "USER": environ.get("TEST_DB_USER", "root"), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", "localhost"), "OPTIONS": { "init_command": "SET default_storage_engine=MyISAM", "charset": "utf8mb4", }, "TEST": { "CHARSET": "utf8mb4", "COLLATION": "utf8mb4_unicode_ci", }, }, "replica": { "ENGINE": "django.db.backends.mysql", "NAME": "pytest_django_tests_replica", "USER": environ.get("TEST_DB_USER", "root"), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", "localhost"), "OPTIONS": { "init_command": "SET default_storage_engine=MyISAM", "charset": "utf8mb4", }, "TEST": { "MIRROR": "default", "CHARSET": "utf8mb4", "COLLATION": "utf8mb4_unicode_ci", }, }, "second": { "ENGINE": "django.db.backends.mysql", "NAME": "pytest_django_tests_second", "USER": environ.get("TEST_DB_USER", "root"), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", "localhost"), "OPTIONS": { "init_command": "SET default_storage_engine=MyISAM", "charset": "utf8mb4", }, "TEST": { "CHARSET": "utf8mb4", "COLLATION": "utf8mb4_unicode_ci", }, }, } pytest-django-4.5.2/pytest_django_test/settings_postgres.py000066400000000000000000000021111415366732400243710ustar00rootroot00000000000000from os import environ from .settings_base import * # noqa: F401 F403 # PyPy compatibility try: from psycopg2cffi import compat compat.register() except ImportError: pass DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "pytest_django_tests_default", "USER": environ.get("TEST_DB_USER", ""), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", ""), }, "replica": { "ENGINE": "django.db.backends.postgresql", "NAME": "pytest_django_tests_replica", "USER": environ.get("TEST_DB_USER", ""), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", ""), "TEST": { "MIRROR": "default", }, }, "second": { "ENGINE": "django.db.backends.postgresql", "NAME": "pytest_django_tests_second", "USER": environ.get("TEST_DB_USER", ""), "PASSWORD": environ.get("TEST_DB_PASSWORD", ""), "HOST": environ.get("TEST_DB_HOST", ""), }, } pytest-django-4.5.2/pytest_django_test/settings_sqlite.py000066400000000000000000000006531415366732400240350ustar00rootroot00000000000000from .settings_base import * # noqa: F401 F403 DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", }, "replica": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", "TEST": { "MIRROR": "default", }, }, "second": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", }, } pytest-django-4.5.2/pytest_django_test/settings_sqlite_file.py000066400000000000000000000020301415366732400250230ustar00rootroot00000000000000import tempfile from .settings_base import * # noqa: F401 F403 # This is a SQLite configuration, which uses a file based database for # tests (via setting TEST_NAME / TEST['NAME']). # The name as expected / used by Django/pytest_django (tests/db_helpers.py). _fd, _filename_default = tempfile.mkstemp(prefix="test_") _fd, _filename_replica = tempfile.mkstemp(prefix="test_") _fd, _filename_second = tempfile.mkstemp(prefix="test_") DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/pytest_django_tests_default", "TEST": { "NAME": _filename_default, }, }, "replica": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/pytest_django_tests_replica", "TEST": { "MIRROR": "default", "NAME": _filename_replica, }, }, "second": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/pytest_django_tests_second", "TEST": { "NAME": _filename_second, }, }, } pytest-django-4.5.2/pytest_django_test/urls.py000066400000000000000000000002541415366732400215760ustar00rootroot00000000000000from django.urls import path from .app import views urlpatterns = [ path("item_count/", views.item_count), path("admin-required/", views.admin_required_view), ] pytest-django-4.5.2/pytest_django_test/urls_overridden.py000066400000000000000000000002471415366732400240210ustar00rootroot00000000000000from django.http import HttpResponse from django.urls import path urlpatterns = [ path("overridden_url/", lambda r: HttpResponse("Overridden urlconf works!")) ] pytest-django-4.5.2/requirements.txt000066400000000000000000000001421415366732400176060ustar00rootroot00000000000000-e . setuptools django django-configurations pytest-xdist tox wheel twine flake8 sphinx_rtd_theme pytest-django-4.5.2/setup.cfg000066400000000000000000000051201415366732400161440ustar00rootroot00000000000000[metadata] name = pytest-django description = A Django plugin for pytest. long_description = file: README.rst long_description_content_type = text/x-rst author = Andreas Pelme author_email = andreas@pelme.se maintainer = Andreas Pelme maintainer_email = andreas@pelme.se url = https://pytest-django.readthedocs.io/ license = BSD-3-Clause license_file = LICENSE classifiers = Development Status :: 5 - Production/Stable Framework :: Django Framework :: Django :: 2.2 Framework :: Django :: 3.1 Framework :: Django :: 3.2 Framework :: Django :: 4.0 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Testing project_urls = Source=https://github.com/pytest-dev/pytest-django Changelog=https://pytest-django.readthedocs.io/en/latest/changelog.html [options] packages = pytest_django python_requires = >=3.5 setup_requires = setuptools_scm>=5.0.0 install_requires = pytest>=5.4.0 zip_safe = no [options.entry_points] pytest11 = django = pytest_django.plugin [options.extras_require] docs = sphinx sphinx_rtd_theme testing = Django django-configurations>=2.0 [options.package_data] pytest_django = py.typed [tool:pytest] # --strict-markers: error on using unregistered marker. # -ra: show extra test summary info for everything. addopts = --strict-markers -ra DJANGO_SETTINGS_MODULE = pytest_django_test.settings_sqlite_file testpaths = tests [flake8] # W503 line break before binary operator ignore = W503 max-line-length = 99 exclude = lib/,src/,docs/,bin/ [isort] forced_separate = tests,pytest_django,pytest_django_test combine_as_imports = true default_section = THIRDPARTY include_trailing_comma = true line_length = 79 multi_line_output = 5 lines_after_imports = 2 [mypy] check_untyped_defs = True disallow_any_generics = True no_implicit_optional = True show_error_codes = True strict_equality = True warn_redundant_casts = True warn_unreachable = True warn_unused_configs = True no_implicit_reexport = True [mypy-django.*] ignore_missing_imports = True [mypy-configurations.*] ignore_missing_imports = True [mypy-psycopg2cffi.*] ignore_missing_imports = True pytest-django-4.5.2/setup.py000077500000000000000000000001051415366732400160360ustar00rootroot00000000000000from setuptools import setup if __name__ == "__main__": setup() pytest-django-4.5.2/tests/000077500000000000000000000000001415366732400154675ustar00rootroot00000000000000pytest-django-4.5.2/tests/conftest.py000066400000000000000000000107231415366732400176710ustar00rootroot00000000000000import copy import pathlib import shutil from textwrap import dedent from typing import Optional import pytest from django.conf import settings pytest_plugins = "pytester" REPOSITORY_ROOT = pathlib.Path(__file__).parent def pytest_configure(config) -> None: config.addinivalue_line( "markers", "django_project: options for the django_testdir fixture" ) def _marker_apifun( extra_settings: str = "", create_manage_py: bool = False, project_root: Optional[str] = None, ): return { "extra_settings": extra_settings, "create_manage_py": create_manage_py, "project_root": project_root, } @pytest.fixture def testdir(testdir, monkeypatch): monkeypatch.delenv("PYTEST_ADDOPTS", raising=False) return testdir @pytest.fixture(scope="function") def django_testdir(request, testdir, monkeypatch): from pytest_django_test.db_helpers import ( DB_NAME, SECOND_DB_NAME, SECOND_TEST_DB_NAME, TEST_DB_NAME, ) marker = request.node.get_closest_marker("django_project") options = _marker_apifun(**(marker.kwargs if marker else {})) if hasattr(request.node.cls, "db_settings"): db_settings = request.node.cls.db_settings else: db_settings = copy.deepcopy(settings.DATABASES) db_settings["default"]["NAME"] = DB_NAME db_settings["default"]["TEST"]["NAME"] = TEST_DB_NAME db_settings["second"]["NAME"] = SECOND_DB_NAME db_settings["second"].setdefault("TEST", {})["NAME"] = SECOND_TEST_DB_NAME test_settings = ( dedent( """ import django # Pypy compatibility try: from psycopg2cffi import compat except ImportError: pass else: compat.register() DATABASES = %(db_settings)s DATABASE_ROUTERS = ['pytest_django_test.db_router.DbRouter'] INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', 'tpkg.app', ] SECRET_KEY = 'foobar' MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': {}, }, ] %(extra_settings)s """ ) % { "db_settings": repr(db_settings), "extra_settings": dedent(options["extra_settings"]), } ) if options["project_root"]: project_root = testdir.mkdir(options["project_root"]) else: project_root = testdir.tmpdir tpkg_path = project_root.mkdir("tpkg") if options["create_manage_py"]: project_root.ensure("manage.py") tpkg_path.ensure("__init__.py") app_source = REPOSITORY_ROOT / "../pytest_django_test/app" test_app_path = tpkg_path.join("app") # Copy the test app to make it available in the new test run shutil.copytree(str(app_source), str(test_app_path)) tpkg_path.join("the_settings.py").write(test_settings) monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "tpkg.the_settings") def create_test_module(test_code: str, filename: str = "test_the_test.py"): r = tpkg_path.join(filename) r.write(dedent(test_code), ensure=True) return r def create_app_file(code: str, filename: str): r = test_app_path.join(filename) r.write(dedent(code), ensure=True) return r testdir.create_test_module = create_test_module testdir.create_app_file = create_app_file testdir.project_root = project_root testdir.makeini( """ [pytest] addopts = --strict-markers console_output_style=classic """ ) return testdir @pytest.fixture def django_testdir_initial(django_testdir): """A django_testdir fixture which provides initial_data.""" django_testdir.project_root.join("tpkg/app/migrations").remove() django_testdir.makefile( ".json", initial_data=""" [{ "pk": 1, "model": "app.item", "fields": { "name": "mark_initial_data" } }]""", ) return django_testdir pytest-django-4.5.2/tests/test_asserts.py000066400000000000000000000030631415366732400205660ustar00rootroot00000000000000""" Tests the dynamic loading of all Django assertion cases. """ import inspect from typing import List import pytest import pytest_django from pytest_django.asserts import __all__ as asserts_all def _get_actual_assertions_names() -> List[str]: """ Returns list with names of all assertion helpers in Django. """ from unittest import TestCase as DefaultTestCase from django.test import TestCase as DjangoTestCase obj = DjangoTestCase('run') def is_assert(func) -> bool: return func.startswith('assert') and '_' not in func base_methods = [name for name, member in inspect.getmembers(DefaultTestCase) if is_assert(name)] return [name for name, member in inspect.getmembers(obj) if is_assert(name) and name not in base_methods] def test_django_asserts_available() -> None: django_assertions = _get_actual_assertions_names() expected_assertions = asserts_all assert set(django_assertions) == set(expected_assertions) for name in expected_assertions: assert hasattr(pytest_django.asserts, name) @pytest.mark.django_db def test_sanity() -> None: from django.http import HttpResponse from pytest_django.asserts import assertContains, assertNumQueries response = HttpResponse('My response') assertContains(response, 'My response') with pytest.raises(AssertionError): assertContains(response, 'Not my response') assertNumQueries(0, lambda: 1 + 1) with assertNumQueries(0): pass assert assertContains.__doc__ pytest-django-4.5.2/tests/test_database.py000066400000000000000000000352441415366732400206540ustar00rootroot00000000000000import pytest from django.db import connection, transaction from pytest_django.lazy_django import get_django_version from pytest_django_test.app.models import Item, SecondItem def db_supports_reset_sequences() -> bool: """Return if the current db engine supports `reset_sequences`.""" return ( connection.features.supports_transactions and connection.features.supports_sequence_reset ) def test_noaccess() -> None: with pytest.raises(RuntimeError): Item.objects.create(name="spam") with pytest.raises(RuntimeError): Item.objects.count() @pytest.fixture def noaccess() -> None: with pytest.raises(RuntimeError): Item.objects.create(name="spam") with pytest.raises(RuntimeError): Item.objects.count() def test_noaccess_fixture(noaccess: None) -> None: # Setup will fail if this test needs to fail pass @pytest.fixture def non_zero_sequences_counter(db: None) -> None: """Ensure that the db's internal sequence counter is > 1. This is used to test the `reset_sequences` feature. """ item_1 = Item.objects.create(name="item_1") item_2 = Item.objects.create(name="item_2") item_1.delete() item_2.delete() class TestDatabaseFixtures: """Tests for the different database fixtures.""" @pytest.fixture(params=[ "db", "transactional_db", "django_db_reset_sequences", "django_db_serialized_rollback", ]) def all_dbs(self, request) -> None: if request.param == "django_db_reset_sequences": return request.getfixturevalue("django_db_reset_sequences") elif request.param == "transactional_db": return request.getfixturevalue("transactional_db") elif request.param == "db": return request.getfixturevalue("db") elif request.param == "django_db_serialized_rollback": return request.getfixturevalue("django_db_serialized_rollback") else: assert False # pragma: no cover def test_access(self, all_dbs: None) -> None: Item.objects.create(name="spam") def test_clean_db(self, all_dbs: None) -> None: # Relies on the order: test_access created an object assert Item.objects.count() == 0 def test_transactions_disabled(self, db: None) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") assert connection.in_atomic_block def test_transactions_enabled(self, transactional_db: None) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") assert not connection.in_atomic_block def test_transactions_enabled_via_reset_seq( self, django_db_reset_sequences: None, ) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") assert not connection.in_atomic_block def test_django_db_reset_sequences_fixture( self, db: None, django_testdir, non_zero_sequences_counter: None, ) -> None: if not db_supports_reset_sequences(): pytest.skip( "transactions and reset_sequences must be supported " "by the database to run this test" ) # The test runs on a database that already contains objects, so its # id counter is > 1. We check for the ids of newly created objects. django_testdir.create_test_module( """ import pytest from .app.models import Item def test_django_db_reset_sequences_requested( django_db_reset_sequences): item = Item.objects.create(name='new_item') assert item.id == 1 """ ) result = django_testdir.runpytest_subprocess("-v", "--reuse-db") result.stdout.fnmatch_lines( ["*test_django_db_reset_sequences_requested PASSED*"] ) def test_serialized_rollback(self, db: None, django_testdir) -> None: django_testdir.create_app_file( """ from django.db import migrations def load_data(apps, schema_editor): Item = apps.get_model("app", "Item") Item.objects.create(name="loaded-in-migration") class Migration(migrations.Migration): dependencies = [ ("app", "0001_initial"), ] operations = [ migrations.RunPython(load_data), ] """, "migrations/0002_data_migration.py", ) django_testdir.create_test_module( """ import pytest from .app.models import Item @pytest.mark.django_db(transaction=True, serialized_rollback=True) def test_serialized_rollback_1(): assert Item.objects.filter(name="loaded-in-migration").exists() @pytest.mark.django_db(transaction=True) def test_serialized_rollback_2(django_db_serialized_rollback): assert Item.objects.filter(name="loaded-in-migration").exists() Item.objects.create(name="test2") @pytest.mark.django_db(transaction=True, serialized_rollback=True) def test_serialized_rollback_3(): assert Item.objects.filter(name="loaded-in-migration").exists() assert not Item.objects.filter(name="test2").exists() """ ) result = django_testdir.runpytest_subprocess("-v") assert result.ret == 0 @pytest.fixture def mydb(self, all_dbs: None) -> None: # This fixture must be able to access the database Item.objects.create(name="spam") def test_mydb(self, mydb: None) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") # Check the fixture had access to the db item = Item.objects.get(name="spam") assert item def test_fixture_clean(self, all_dbs: None) -> None: # Relies on the order: test_mydb created an object # See https://github.com/pytest-dev/pytest-django/issues/17 assert Item.objects.count() == 0 @pytest.fixture def fin(self, request, all_dbs: None) -> None: # This finalizer must be able to access the database request.addfinalizer(lambda: Item.objects.create(name="spam")) def test_fin(self, fin: None) -> None: # Check finalizer has db access (teardown will fail if not) pass @pytest.mark.skipif(get_django_version() < (3, 2), reason="Django >= 3.2 required") def test_durable_transactions(self, all_dbs: None) -> None: with transaction.atomic(durable=True): item = Item.objects.create(name="foo") assert Item.objects.get() == item class TestDatabaseFixturesAllOrder: @pytest.fixture def fixture_with_db(self, db: None) -> None: Item.objects.create(name="spam") @pytest.fixture def fixture_with_transdb(self, transactional_db: None) -> None: Item.objects.create(name="spam") @pytest.fixture def fixture_with_reset_sequences(self, django_db_reset_sequences: None) -> None: Item.objects.create(name="spam") @pytest.fixture def fixture_with_serialized_rollback(self, django_db_serialized_rollback: None) -> None: Item.objects.create(name="ham") def test_trans(self, fixture_with_transdb: None) -> None: pass def test_db(self, fixture_with_db: None) -> None: pass def test_db_trans(self, fixture_with_db: None, fixture_with_transdb: None) -> None: pass def test_trans_db(self, fixture_with_transdb: None, fixture_with_db: None) -> None: pass def test_reset_sequences( self, fixture_with_reset_sequences: None, fixture_with_transdb: None, fixture_with_db: None, ) -> None: pass # The test works when transactions are not supported, but it interacts # badly with other tests. @pytest.mark.skipif('not connection.features.supports_transactions') def test_serialized_rollback( self, fixture_with_serialized_rollback: None, fixture_with_db: None, ) -> None: pass class TestDatabaseMarker: "Tests for the django_db marker." @pytest.mark.django_db def test_access(self) -> None: Item.objects.create(name="spam") @pytest.mark.django_db def test_clean_db(self) -> None: # Relies on the order: test_access created an object. assert Item.objects.count() == 0 @pytest.mark.django_db def test_transactions_disabled(self) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") assert connection.in_atomic_block @pytest.mark.django_db(transaction=False) def test_transactions_disabled_explicit(self) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") assert connection.in_atomic_block @pytest.mark.django_db(transaction=True) def test_transactions_enabled(self) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") assert not connection.in_atomic_block @pytest.mark.django_db def test_reset_sequences_disabled(self, request) -> None: marker = request.node.get_closest_marker("django_db") assert not marker.kwargs @pytest.mark.django_db(reset_sequences=True) def test_reset_sequences_enabled(self, request) -> None: marker = request.node.get_closest_marker("django_db") assert marker.kwargs["reset_sequences"] @pytest.mark.django_db(transaction=True, reset_sequences=True) def test_transaction_reset_sequences_enabled(self, request) -> None: marker = request.node.get_closest_marker("django_db") assert marker.kwargs["reset_sequences"] @pytest.mark.django_db(databases=['default', 'replica', 'second']) def test_databases(self, request) -> None: marker = request.node.get_closest_marker("django_db") assert marker.kwargs["databases"] == ['default', 'replica', 'second'] @pytest.mark.django_db(databases=['second']) def test_second_database(self, request) -> None: SecondItem.objects.create(name="spam") @pytest.mark.django_db(databases=['default']) def test_not_allowed_database(self, request) -> None: with pytest.raises(AssertionError, match='not allowed'): SecondItem.objects.count() with pytest.raises(AssertionError, match='not allowed'): SecondItem.objects.create(name="spam") @pytest.mark.django_db(databases=['replica']) def test_replica_database(self, request) -> None: Item.objects.using('replica').count() @pytest.mark.django_db(databases=['replica']) def test_replica_database_not_allowed(self, request) -> None: with pytest.raises(AssertionError, match='not allowed'): Item.objects.count() @pytest.mark.django_db(transaction=True, databases=['default', 'replica']) def test_replica_mirrors_default_database(self, request) -> None: Item.objects.create(name='spam') Item.objects.using('replica').create(name='spam') assert Item.objects.count() == 2 assert Item.objects.using('replica').count() == 2 @pytest.mark.django_db(databases='__all__') def test_all_databases(self, request) -> None: Item.objects.count() Item.objects.create(name="spam") SecondItem.objects.count() SecondItem.objects.create(name="spam") @pytest.mark.django_db def test_serialized_rollback_disabled(self, request): marker = request.node.get_closest_marker("django_db") assert not marker.kwargs # The test works when transactions are not supported, but it interacts # badly with other tests. @pytest.mark.skipif('not connection.features.supports_transactions') @pytest.mark.django_db(serialized_rollback=True) def test_serialized_rollback_enabled(self, request): marker = request.node.get_closest_marker("django_db") assert marker.kwargs["serialized_rollback"] def test_unittest_interaction(django_testdir) -> None: "Test that (non-Django) unittests cannot access the DB." django_testdir.create_test_module( """ import pytest import unittest from .app.models import Item class TestCase_setupClass(unittest.TestCase): @classmethod def setUpClass(cls): Item.objects.create(name='foo') def test_db_access_1(self): Item.objects.count() == 1 class TestCase_setUp(unittest.TestCase): @classmethod def setUp(cls): Item.objects.create(name='foo') def test_db_access_2(self): Item.objects.count() == 1 class TestCase(unittest.TestCase): def test_db_access_3(self): Item.objects.count() == 1 """ ) result = django_testdir.runpytest_subprocess("-v", "--reuse-db") result.stdout.fnmatch_lines( [ "*test_db_access_1 ERROR*", "*test_db_access_2 FAILED*", "*test_db_access_3 FAILED*", "*ERROR at setup of TestCase_setupClass.test_db_access_1*", '*RuntimeError: Database access not allowed, use the "django_db" mark, ' 'or the "db" or "transactional_db" fixtures to enable it.', ] ) class Test_database_blocking: def test_db_access_in_conftest(self, django_testdir) -> None: """Make sure database access in conftest module is prohibited.""" django_testdir.makeconftest( """ from tpkg.app.models import Item Item.objects.get() """ ) result = django_testdir.runpytest_subprocess("-v") result.stderr.fnmatch_lines( [ '*RuntimeError: Database access not allowed, use the "django_db" mark, ' 'or the "db" or "transactional_db" fixtures to enable it.*' ] ) def test_db_access_in_test_module(self, django_testdir) -> None: django_testdir.create_test_module( """ from tpkg.app.models import Item Item.objects.get() """ ) result = django_testdir.runpytest_subprocess("-v") result.stdout.fnmatch_lines( [ '*RuntimeError: Database access not allowed, use the "django_db" mark, ' 'or the "db" or "transactional_db" fixtures to enable it.' ] ) pytest-django-4.5.2/tests/test_db_access_in_repr.py000066400000000000000000000016771415366732400225370ustar00rootroot00000000000000def test_db_access_with_repr_in_report(django_testdir) -> None: django_testdir.create_test_module( """ import pytest from .app.models import Item def test_via_db_blocker(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): Item.objects.get(name='This one is not there') def test_via_db_fixture(db): Item.objects.get(name='This one is not there') """ ) result = django_testdir.runpytest_subprocess("--tb=auto") result.stdout.fnmatch_lines([ "tpkg/test_the_test.py FF", "E *DoesNotExist: Item matching query does not exist.", "tpkg/test_the_test.py:8: ", 'self = *RuntimeError*Database access not allowed*', "E *DoesNotExist: Item matching query does not exist.", "* 2 failed*", ]) assert "INTERNALERROR" not in str(result.stdout) + str(result.stderr) assert result.ret == 1 pytest-django-4.5.2/tests/test_db_setup.py000066400000000000000000000417511415366732400207150ustar00rootroot00000000000000import pytest from pytest_django_test.db_helpers import ( db_exists, drop_database, mark_database, mark_exists, skip_if_sqlite_in_memory, ) def test_db_reuse_simple(django_testdir) -> None: "A test for all backends to check that `--reuse-db` works." django_testdir.create_test_module( """ import pytest from .app.models import Item @pytest.mark.django_db def test_db_can_be_accessed(): assert Item.objects.count() == 0 """ ) result = django_testdir.runpytest_subprocess("-v", "--reuse-db") assert result.ret == 0 result.stdout.fnmatch_lines(["*test_db_can_be_accessed PASSED*"]) def test_db_order(django_testdir) -> None: """Test order in which tests are being executed.""" django_testdir.create_test_module(''' import pytest from unittest import TestCase from django.test import SimpleTestCase from django.test import TestCase as DjangoTestCase from django.test import TransactionTestCase from .app.models import Item @pytest.mark.django_db(transaction=True) def test_run_second_decorator(): pass def test_run_second_fixture(transactional_db): pass def test_run_second_reset_sequences_fixture(django_db_reset_sequences): pass class MyTransactionTestCase(TransactionTestCase): def test_run_second_transaction_test_case(self): pass def test_run_first_fixture(db): pass class TestClass: def test_run_second_fixture_class(self, transactional_db): pass def test_run_first_fixture_class(self, db): pass @pytest.mark.django_db(reset_sequences=True) def test_run_second_reset_sequences_decorator(): pass class MyDjangoTestCase(DjangoTestCase): def test_run_first_django_test_case(self): pass class MySimpleTestCase(SimpleTestCase): def test_run_last_simple_test_case(self): pass @pytest.mark.django_db def test_run_first_decorator(): pass @pytest.mark.django_db(serialized_rollback=True) def test_run_first_serialized_rollback_decorator(): pass class MyTestCase(TestCase): def test_run_last_test_case(self): pass ''') result = django_testdir.runpytest_subprocess('-q', '--collect-only') assert result.ret == 0 result.stdout.fnmatch_lines([ "*test_run_first_fixture*", "*test_run_first_fixture_class*", "*test_run_first_django_test_case*", "*test_run_first_decorator*", "*test_run_first_serialized_rollback_decorator*", "*test_run_second_decorator*", "*test_run_second_fixture*", "*test_run_second_reset_sequences_fixture*", "*test_run_second_transaction_test_case*", "*test_run_second_fixture_class*", "*test_run_second_reset_sequences_decorator*", "*test_run_last_simple_test_case*", "*test_run_last_test_case*", ], consecutive=True) def test_db_reuse(django_testdir) -> None: """ Test the re-use db functionality. """ skip_if_sqlite_in_memory() django_testdir.create_test_module( """ import pytest from .app.models import Item @pytest.mark.django_db def test_db_can_be_accessed(): assert Item.objects.count() == 0 """ ) # Use --create-db on the first run to make sure we are not just re-using a # database from another test run drop_database() assert not db_exists() # Do not pass in --create-db to make sure it is created when it # does not exist result_first = django_testdir.runpytest_subprocess("-v", "--reuse-db") assert result_first.ret == 0 result_first.stdout.fnmatch_lines(["*test_db_can_be_accessed PASSED*"]) assert not mark_exists() mark_database() assert mark_exists() result_second = django_testdir.runpytest_subprocess("-v", "--reuse-db") assert result_second.ret == 0 result_second.stdout.fnmatch_lines(["*test_db_can_be_accessed PASSED*"]) # Make sure the database has not been re-created assert mark_exists() result_third = django_testdir.runpytest_subprocess( "-v", "--reuse-db", "--create-db" ) assert result_third.ret == 0 result_third.stdout.fnmatch_lines(["*test_db_can_be_accessed PASSED*"]) # Make sure the database has been re-created and the mark is gone assert db_exists() assert not mark_exists() class TestSqlite: db_settings = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "db_name", "TEST": {"NAME": "test_custom_db_name"}, } } def test_sqlite_test_name_used(self, django_testdir) -> None: django_testdir.create_test_module( """ import pytest from django.db import connections from django import VERSION @pytest.mark.django_db def test_a(): (conn, ) = connections.all() assert conn.vendor == 'sqlite' print(conn.settings_dict) assert conn.settings_dict['NAME'] == 'test_custom_db_name' """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-v") assert result.ret == 0 result.stdout.fnmatch_lines(["*test_a*PASSED*"]) def test_xdist_with_reuse(django_testdir) -> None: pytest.importorskip("xdist") skip_if_sqlite_in_memory() drop_database("gw0") drop_database("gw1") assert not db_exists("gw0") assert not db_exists("gw1") django_testdir.create_test_module( """ import pytest from .app.models import Item def _check(settings): # Make sure that the database name looks correct db_name = settings.DATABASES['default']['NAME'] assert db_name.endswith('_gw0') or db_name.endswith('_gw1') assert Item.objects.count() == 0 Item.objects.create(name='foo') assert Item.objects.count() == 1 @pytest.mark.django_db def test_a(settings): _check(settings) @pytest.mark.django_db def test_b(settings): _check(settings) @pytest.mark.django_db def test_c(settings): _check(settings) @pytest.mark.django_db def test_d(settings): _check(settings) """ ) result = django_testdir.runpytest_subprocess("-vv", "-n2", "-s", "--reuse-db") assert result.ret == 0 result.stdout.fnmatch_lines(["*PASSED*test_a*"]) result.stdout.fnmatch_lines(["*PASSED*test_b*"]) result.stdout.fnmatch_lines(["*PASSED*test_c*"]) result.stdout.fnmatch_lines(["*PASSED*test_d*"]) assert db_exists("gw0") assert db_exists("gw1") result = django_testdir.runpytest_subprocess("-vv", "-n2", "-s", "--reuse-db") assert result.ret == 0 result.stdout.fnmatch_lines(["*PASSED*test_a*"]) result.stdout.fnmatch_lines(["*PASSED*test_b*"]) result.stdout.fnmatch_lines(["*PASSED*test_c*"]) result.stdout.fnmatch_lines(["*PASSED*test_d*"]) result = django_testdir.runpytest_subprocess( "-vv", "-n2", "-s", "--reuse-db", "--create-db" ) assert result.ret == 0 result.stdout.fnmatch_lines(["*PASSED*test_a*"]) result.stdout.fnmatch_lines(["*PASSED*test_b*"]) result.stdout.fnmatch_lines(["*PASSED*test_c*"]) result.stdout.fnmatch_lines(["*PASSED*test_d*"]) # Cleanup. drop_database("gw0") drop_database("gw1") class TestSqliteWithXdist: db_settings = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/tmp/should-not-be-used", } } def test_sqlite_in_memory_used(self, django_testdir) -> None: pytest.importorskip("xdist") django_testdir.create_test_module( """ import pytest from django.db import connections @pytest.mark.django_db def test_a(): (conn, ) = connections.all() assert conn.vendor == 'sqlite' db_name = conn.creation._get_test_db_name() assert 'file:memorydb' in db_name or db_name == ':memory:' """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1") assert result.ret == 0 result.stdout.fnmatch_lines(["*PASSED*test_a*"]) class TestSqliteWithMultipleDbsAndXdist: db_settings = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/tmp/should-not-be-used", }, "db2": { "ENGINE": "django.db.backends.sqlite3", "NAME": "db_name", "TEST": {"NAME": "test_custom_db_name"}, } } def test_sqlite_database_renamed(self, django_testdir) -> None: pytest.importorskip("xdist") django_testdir.create_test_module( """ import pytest from django.db import connections @pytest.mark.django_db def test_a(): (conn_db2, conn_default) = sorted( connections.all(), key=lambda conn: conn.alias, ) assert conn_default.vendor == 'sqlite' db_name = conn_default.creation._get_test_db_name() # can_share_in_memory_db was removed in Django 2.1, and # used in _get_test_db_name before. if getattr(conn_default.features, "can_share_in_memory_db", True): assert 'file:memorydb' in db_name else: assert db_name == ":memory:" assert conn_db2.vendor == 'sqlite' db_name = conn_db2.creation._get_test_db_name() assert db_name.startswith('test_custom_db_name_gw') """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1") assert result.ret == 0 result.stdout.fnmatch_lines(["*PASSED*test_a*"]) class TestSqliteWithTox: db_settings = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "db_name", "TEST": {"NAME": "test_custom_db_name"}, } } def test_db_with_tox_suffix(self, django_testdir, monkeypatch) -> None: "A test to check that Tox DB suffix works when running in parallel." monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django22") django_testdir.create_test_module( """ import pytest from django.db import connections @pytest.mark.django_db def test_inner(): (conn, ) = connections.all() assert conn.vendor == 'sqlite' db_name = conn.creation._get_test_db_name() assert db_name == 'test_custom_db_name_py37-django22' """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-vv") assert result.ret == 0 result.stdout.fnmatch_lines(["*test_inner*PASSED*"]) def test_db_with_empty_tox_suffix(self, django_testdir, monkeypatch) -> None: "A test to check that Tox DB suffix is not used when suffix would be empty." monkeypatch.setenv("TOX_PARALLEL_ENV", "") django_testdir.create_test_module( """ import pytest from django.db import connections @pytest.mark.django_db def test_inner(): (conn,) = connections.all() assert conn.vendor == 'sqlite' db_name = conn.creation._get_test_db_name() assert db_name == 'test_custom_db_name' """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-vv") assert result.ret == 0 result.stdout.fnmatch_lines(["*test_inner*PASSED*"]) class TestSqliteWithToxAndXdist: db_settings = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "db_name", "TEST": {"NAME": "test_custom_db_name"}, } } def test_db_with_tox_suffix(self, django_testdir, monkeypatch) -> None: "A test to check that both Tox and xdist suffixes work together." pytest.importorskip("xdist") monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django22") django_testdir.create_test_module( """ import pytest from django.db import connections @pytest.mark.django_db def test_inner(): (conn, ) = connections.all() assert conn.vendor == 'sqlite' db_name = conn.creation._get_test_db_name() assert db_name.startswith('test_custom_db_name_py37-django22_gw') """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1") assert result.ret == 0 result.stdout.fnmatch_lines(["*PASSED*test_inner*"]) class TestSqliteInMemoryWithXdist: db_settings = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", "TEST": {"NAME": ":memory:"}, } } def test_sqlite_in_memory_used(self, django_testdir) -> None: pytest.importorskip("xdist") django_testdir.create_test_module( """ import pytest from django.db import connections @pytest.mark.django_db def test_a(): (conn, ) = connections.all() assert conn.vendor == 'sqlite' db_name = conn.creation._get_test_db_name() assert 'file:memorydb' in db_name or db_name == ':memory:' """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1") assert result.ret == 0 result.stdout.fnmatch_lines(["*PASSED*test_a*"]) class TestMigrations: """Tests for Django Migrations.""" def test_no_migrations(self, django_testdir) -> None: django_testdir.create_test_module( """ import pytest @pytest.mark.django_db def test_inner_migrations(): from .app.models import Item Item.objects.create() """ ) django_testdir.create_test_module( """ raise Exception("This should not get imported.") """, "migrations/0001_initial.py", ) result = django_testdir.runpytest_subprocess( "--nomigrations", "--tb=short", "-vv", "-s", ) assert result.ret == 0 assert "Operations to perform:" not in result.stdout.str() result.stdout.fnmatch_lines(["*= 1 passed*"]) def test_migrations_run(self, django_testdir) -> None: testdir = django_testdir testdir.create_test_module( """ import pytest @pytest.mark.django_db def test_inner_migrations(): from .app.models import Item Item.objects.create() """ ) testdir.create_app_file( """ from django.db import migrations, models def print_it(apps, schema_editor): print("mark_migrations_run") class Migration(migrations.Migration): dependencies = [] operations = [ migrations.CreateModel( name='Item', fields=[ ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True)), ('name', models.CharField(max_length=100)), ], options={ }, bases=(models.Model,), ), migrations.CreateModel( name='SecondItem', fields=[ ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True)), ('name', models.CharField(max_length=100)), ], ), migrations.RunPython( print_it, ), ] """, "migrations/0001_initial.py", ) result = testdir.runpytest_subprocess("--tb=short", "-v", "-s") assert result.ret == 0 result.stdout.fnmatch_lines(["*mark_migrations_run*"]) result = testdir.runpytest_subprocess( "--no-migrations", "--migrations", "--tb=short", "-v", "-s" ) assert result.ret == 0 result.stdout.fnmatch_lines(["*mark_migrations_run*"]) pytest-django-4.5.2/tests/test_django_configurations.py000066400000000000000000000077121415366732400234630ustar00rootroot00000000000000"""Tests which check the various ways you can set DJANGO_SETTINGS_MODULE If these tests fail you probably forgot to install django-configurations. """ import pytest pytest.importorskip("configurations") BARE_SETTINGS = """ from configurations import Configuration class MySettings(Configuration): # At least one database must be configured DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' }, } SECRET_KEY = 'foobar' """ def test_dc_env(testdir, monkeypatch) -> None: monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "tpkg.settings_env") monkeypatch.setenv("DJANGO_CONFIGURATION", "MySettings") pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_env.py") settings.write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_settings(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_env' assert os.environ['DJANGO_CONFIGURATION'] == 'MySettings' """ ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines([ 'django: settings: tpkg.settings_env (from env), configuration: MySettings (from env)', "* 1 passed*", ]) assert result.ret == 0 def test_dc_env_overrides_ini(testdir, monkeypatch) -> None: monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "tpkg.settings_env") monkeypatch.setenv("DJANGO_CONFIGURATION", "MySettings") testdir.makeini( """ [pytest] DJANGO_SETTINGS_MODULE = DO_NOT_USE_ini DJANGO_CONFIGURATION = DO_NOT_USE_ini """ ) pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_env.py") settings.write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_ds(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_env' assert os.environ['DJANGO_CONFIGURATION'] == 'MySettings' """ ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines([ 'django: settings: tpkg.settings_env (from env), configuration: MySettings (from env)', "* 1 passed*", ]) assert result.ret == 0 def test_dc_ini(testdir, monkeypatch) -> None: monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makeini( """ [pytest] DJANGO_SETTINGS_MODULE = tpkg.settings_ini DJANGO_CONFIGURATION = MySettings """ ) pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_ini.py") settings.write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_ds(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_ini' assert os.environ['DJANGO_CONFIGURATION'] == 'MySettings' """ ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines([ 'django: settings: tpkg.settings_ini (from ini), configuration: MySettings (from ini)', "* 1 passed*", ]) assert result.ret == 0 def test_dc_option(testdir, monkeypatch) -> None: monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "DO_NOT_USE_env") monkeypatch.setenv("DJANGO_CONFIGURATION", "DO_NOT_USE_env") testdir.makeini( """ [pytest] DJANGO_SETTINGS_MODULE = DO_NOT_USE_ini DJANGO_CONFIGURATION = DO_NOT_USE_ini """ ) pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_opt.py") settings.write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_ds(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_opt' assert os.environ['DJANGO_CONFIGURATION'] == 'MySettings' """ ) result = testdir.runpytest_subprocess("--ds=tpkg.settings_opt", "--dc=MySettings") result.stdout.fnmatch_lines([ 'django: settings: tpkg.settings_opt (from option),' ' configuration: MySettings (from option)', "* 1 passed*", ]) assert result.ret == 0 pytest-django-4.5.2/tests/test_django_settings_module.py000066400000000000000000000352671415366732400236440ustar00rootroot00000000000000"""Tests which check the various ways you can set DJANGO_SETTINGS_MODULE If these tests fail you probably forgot to run "python setup.py develop". """ import pytest BARE_SETTINGS = """ # At least one database must be configured DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' }, } SECRET_KEY = 'foobar' """ def test_ds_ini(testdir, monkeypatch) -> None: monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makeini( """ [pytest] DJANGO_SETTINGS_MODULE = tpkg.settings_ini """ ) pkg = testdir.mkpydir("tpkg") pkg.join("settings_ini.py").write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_ds(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_ini' """ ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines([ "django: settings: tpkg.settings_ini (from ini)", "*= 1 passed*", ]) assert result.ret == 0 def test_ds_env(testdir, monkeypatch) -> None: monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "tpkg.settings_env") pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_env.py") settings.write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_settings(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_env' """ ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines([ "django: settings: tpkg.settings_env (from env)", "*= 1 passed*", ]) def test_ds_option(testdir, monkeypatch) -> None: monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "DO_NOT_USE_env") testdir.makeini( """ [pytest] DJANGO_SETTINGS_MODULE = DO_NOT_USE_ini """ ) pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_opt.py") settings.write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_ds(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_opt' """ ) result = testdir.runpytest_subprocess("--ds=tpkg.settings_opt") result.stdout.fnmatch_lines([ "django: settings: tpkg.settings_opt (from option)", "*= 1 passed*", ]) def test_ds_env_override_ini(testdir, monkeypatch) -> None: "DSM env should override ini." monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "tpkg.settings_env") testdir.makeini( """\ [pytest] DJANGO_SETTINGS_MODULE = DO_NOT_USE_ini """ ) pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_env.py") settings.write(BARE_SETTINGS) testdir.makepyfile( """ import os def test_ds(): assert os.environ['DJANGO_SETTINGS_MODULE'] == 'tpkg.settings_env' """ ) result = testdir.runpytest_subprocess() assert result.parseoutcomes()["passed"] == 1 assert result.ret == 0 def test_ds_non_existent(testdir, monkeypatch) -> None: """ Make sure we do not fail with INTERNALERROR if an incorrect DJANGO_SETTINGS_MODULE is given. """ monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "DOES_NOT_EXIST") testdir.makepyfile("def test_ds(): pass") result = testdir.runpytest_subprocess() result.stderr.fnmatch_lines(["*ImportError:*DOES_NOT_EXIST*"]) assert result.ret != 0 def test_ds_after_user_conftest(testdir, monkeypatch) -> None: """ Test that the settings module can be imported, after pytest has adjusted the sys.path. """ monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "settings_after_conftest") testdir.makepyfile("def test_ds(): pass") testdir.makepyfile(settings_after_conftest="SECRET_KEY='secret'") # testdir.makeconftest("import sys; print(sys.path)") result = testdir.runpytest_subprocess("-v") result.stdout.fnmatch_lines(["* 1 passed*"]) assert result.ret == 0 def test_ds_in_pytest_configure(testdir, monkeypatch) -> None: monkeypatch.delenv("DJANGO_SETTINGS_MODULE") pkg = testdir.mkpydir("tpkg") settings = pkg.join("settings_ds.py") settings.write(BARE_SETTINGS) testdir.makeconftest( """ import os from django.conf import settings def pytest_configure(): if not settings.configured: os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tpkg.settings_ds') """ ) testdir.makepyfile( """ def test_anything(): pass """ ) r = testdir.runpytest_subprocess() assert r.parseoutcomes()["passed"] == 1 assert r.ret == 0 def test_django_settings_configure(testdir, monkeypatch) -> None: """ Make sure Django can be configured without setting DJANGO_SETTINGS_MODULE altogether, relying on calling django.conf.settings.configure() and then invoking pytest. """ monkeypatch.delenv("DJANGO_SETTINGS_MODULE") p = testdir.makepyfile( run=""" from django.conf import settings settings.configure(SECRET_KEY='set from settings.configure()', DATABASES={'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' }}, INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes',]) import pytest pytest.main() """ ) testdir.makepyfile( """ import pytest from django.conf import settings from django.test.client import RequestFactory from django.test import TestCase from django.contrib.auth.models import User def test_access_to_setting(): assert settings.SECRET_KEY == 'set from settings.configure()' # This test requires Django to be properly configured to be run def test_rf(rf): assert isinstance(rf, RequestFactory) # This tests that pytest-django actually configures the database # according to the settings above class ATestCase(TestCase): def test_user_count(self): assert User.objects.count() == 0 @pytest.mark.django_db def test_user_count(): assert User.objects.count() == 0 """ ) result = testdir.runpython(p) result.stdout.fnmatch_lines(["* 4 passed*"]) def test_settings_in_hook(testdir, monkeypatch) -> None: monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makeconftest( """ from django.conf import settings def pytest_configure(): settings.configure(SECRET_KEY='set from pytest_configure', DATABASES={'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}, INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes',]) """ ) testdir.makepyfile( """ import pytest from django.conf import settings from django.contrib.auth.models import User def test_access_to_setting(): assert settings.SECRET_KEY == 'set from pytest_configure' @pytest.mark.django_db def test_user_count(): assert User.objects.count() == 0 """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 def test_django_not_loaded_without_settings(testdir, monkeypatch) -> None: """ Make sure Django is not imported at all if no Django settings is specified. """ monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makepyfile( """ import sys def test_settings(): assert 'django' not in sys.modules """ ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines(["* 1 passed*"]) assert result.ret == 0 def test_debug_false_by_default(testdir, monkeypatch) -> None: monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makeconftest( """ from django.conf import settings def pytest_configure(): settings.configure(SECRET_KEY='set from pytest_configure', DEBUG=True, DATABASES={'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}, INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes',]) """ ) testdir.makepyfile( """ from django.conf import settings def test_debug_is_false(): assert settings.DEBUG is False """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 @pytest.mark.parametrize('django_debug_mode', (False, True)) def test_django_debug_mode_true_false(testdir, monkeypatch, django_debug_mode: bool) -> None: monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makeini( """ [pytest] django_debug_mode = {} """.format(django_debug_mode) ) testdir.makeconftest( """ from django.conf import settings def pytest_configure(): settings.configure(SECRET_KEY='set from pytest_configure', DEBUG=%s, DATABASES={'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}, INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes',]) """ % (not django_debug_mode) ) testdir.makepyfile( """ from django.conf import settings def test_debug_is_false(): assert settings.DEBUG is {} """.format(django_debug_mode) ) r = testdir.runpytest_subprocess() assert r.ret == 0 @pytest.mark.parametrize('settings_debug', (False, True)) def test_django_debug_mode_keep(testdir, monkeypatch, settings_debug: bool) -> None: monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makeini( """ [pytest] django_debug_mode = keep """ ) testdir.makeconftest( """ from django.conf import settings def pytest_configure(): settings.configure(SECRET_KEY='set from pytest_configure', DEBUG=%s, DATABASES={'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}, INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes',]) """ % settings_debug ) testdir.makepyfile( """ from django.conf import settings def test_debug_is_false(): assert settings.DEBUG is {} """.format(settings_debug) ) r = testdir.runpytest_subprocess() assert r.ret == 0 @pytest.mark.django_project( extra_settings=""" INSTALLED_APPS = [ 'tpkg.app.apps.TestApp', ] """ ) def test_django_setup_sequence(django_testdir) -> None: django_testdir.create_app_file( """ from django.apps import apps, AppConfig class TestApp(AppConfig): name = 'tpkg.app' def ready(self): populating = apps.loading print('READY(): populating=%r' % populating) """, "apps.py", ) django_testdir.create_app_file( """ from django.apps import apps populating = apps.loading print('IMPORT: populating=%r,ready=%r' % (populating, apps.ready)) SOME_THING = 1234 """, "models.py", ) django_testdir.create_app_file("", "__init__.py") django_testdir.makepyfile( """ from django.apps import apps from tpkg.app.models import SOME_THING def test_anything(): populating = apps.loading print('TEST: populating=%r,ready=%r' % (populating, apps.ready)) """ ) result = django_testdir.runpytest_subprocess("-s", "--tb=line") result.stdout.fnmatch_lines(["*IMPORT: populating=True,ready=False*"]) result.stdout.fnmatch_lines(["*READY(): populating=True*"]) result.stdout.fnmatch_lines(["*TEST: populating=True,ready=True*"]) assert result.ret == 0 def test_no_ds_but_django_imported(testdir, monkeypatch) -> None: """pytest-django should not bail out, if "django" has been imported somewhere, e.g. via pytest-splinter.""" monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makepyfile( """ import os import django from pytest_django.lazy_django import django_settings_is_configured def test_django_settings_is_configured(): assert django_settings_is_configured() is False def test_env(): assert 'DJANGO_SETTINGS_MODULE' not in os.environ def test_cfg(pytestconfig): assert pytestconfig.option.ds is None """ ) r = testdir.runpytest_subprocess("-s") assert r.ret == 0 def test_no_ds_but_django_conf_imported(testdir, monkeypatch) -> None: """pytest-django should not bail out, if "django.conf" has been imported somewhere, e.g. via hypothesis (#599).""" monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makepyfile( """ import os import sys # line copied from hypothesis/extras/django.py from django.conf import settings as django_settings # Don't let pytest poke into this object, generating a # django.core.exceptions.ImproperlyConfigured del django_settings from pytest_django.lazy_django import django_settings_is_configured def test_django_settings_is_configured(): assert django_settings_is_configured() is False def test_django_conf_is_imported(): assert 'django.conf' in sys.modules def test_env(): assert 'DJANGO_SETTINGS_MODULE' not in os.environ def test_cfg(pytestconfig): assert pytestconfig.option.ds is None """ ) r = testdir.runpytest_subprocess("-s") assert r.ret == 0 def test_no_django_settings_but_django_imported(testdir, monkeypatch) -> None: """Make sure we do not crash when Django happens to be imported, but settings is not properly configured""" monkeypatch.delenv("DJANGO_SETTINGS_MODULE") testdir.makeconftest("import django") r = testdir.runpytest_subprocess("--help") assert r.ret == 0 pytest-django-4.5.2/tests/test_doctest.txt000066400000000000000000000001201415366732400207250ustar00rootroot00000000000000This doctest should run without problems with pytest. >>> print('works') works pytest-django-4.5.2/tests/test_environment.py000066400000000000000000000221301415366732400214420ustar00rootroot00000000000000import os import pytest from django.contrib.sites import models as site_models from django.contrib.sites.models import Site from django.core import mail from django.db import connection from django.test import TestCase from pytest_django_test.app.models import Item # It doesn't matter which order all the _again methods are run, we just need # to check the environment remains constant. # This is possible with some of the testdir magic, but this is the lazy way # to do it. @pytest.mark.parametrize("subject", ["subject1", "subject2"]) def test_autoclear_mailbox(subject: str) -> None: assert len(mail.outbox) == 0 mail.send_mail(subject, "body", "from@example.com", ["to@example.com"]) assert len(mail.outbox) == 1 m = mail.outbox[0] assert m.subject == subject assert m.body == "body" assert m.from_email == "from@example.com" assert m.to == ["to@example.com"] class TestDirectAccessWorksForDjangoTestCase(TestCase): def _do_test(self) -> None: assert len(mail.outbox) == 0 mail.send_mail("subject", "body", "from@example.com", ["to@example.com"]) assert len(mail.outbox) == 1 def test_one(self) -> None: self._do_test() def test_two(self) -> None: self._do_test() @pytest.mark.django_project( extra_settings=""" TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ) ROOT_URLCONF = 'tpkg.app.urls' """ ) def test_invalid_template_variable(django_testdir) -> None: django_testdir.create_app_file( """ from django.urls import path from tpkg.app import views urlpatterns = [path('invalid_template/', views.invalid_template)] """, "urls.py", ) django_testdir.create_app_file( """ from django.shortcuts import render def invalid_template(request): return render(request, 'invalid_template.html', {}) """, "views.py", ) django_testdir.create_app_file( "
{{ invalid_var }}
", "templates/invalid_template_base.html" ) django_testdir.create_app_file( "{% include 'invalid_template_base.html' %}", "templates/invalid_template.html" ) django_testdir.create_test_module( """ import pytest def test_for_invalid_template(client): client.get('/invalid_template/') @pytest.mark.ignore_template_errors def test_ignore(client): client.get('/invalid_template/') """ ) result = django_testdir.runpytest_subprocess("-s", "--fail-on-template-vars") origin = "'*/tpkg/app/templates/invalid_template_base.html'" result.stdout.fnmatch_lines_random( [ "tpkg/test_the_test.py F.*", "E * Failed: Undefined template variable 'invalid_var' in {}".format( origin ), ] ) @pytest.mark.django_project( extra_settings=""" TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ) ROOT_URLCONF = 'tpkg.app.urls' """ ) def test_invalid_template_with_default_if_none(django_testdir) -> None: django_testdir.create_app_file( """
{{ data.empty|default:'d' }}
{{ data.none|default:'d' }}
{{ data.empty|default_if_none:'d' }}
{{ data.none|default_if_none:'d' }}
{{ data.missing|default_if_none:'d' }}
""", "templates/the_template.html", ) django_testdir.create_test_module( """ def test_for_invalid_template(): from django.shortcuts import render render( request=None, template_name='the_template.html', context={'data': {'empty': '', 'none': None}}, ) """ ) result = django_testdir.runpytest_subprocess("--fail-on-template-vars") result.stdout.fnmatch_lines( [ "tpkg/test_the_test.py F", "E * Failed: Undefined template variable 'data.missing' in *the_template.html'", ] ) @pytest.mark.django_project( extra_settings=""" TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ) ROOT_URLCONF = 'tpkg.app.urls' """ ) def test_invalid_template_variable_opt_in(django_testdir) -> None: django_testdir.create_app_file( """ from django.urls import path from tpkg.app import views urlpatterns = [path('invalid_template', views.invalid_template)] """, "urls.py", ) django_testdir.create_app_file( """ from django.shortcuts import render def invalid_template(request): return render(request, 'invalid_template.html', {}) """, "views.py", ) django_testdir.create_app_file( "
{{ invalid_var }}
", "templates/invalid_template.html" ) django_testdir.create_test_module( """ import pytest def test_for_invalid_template(client): client.get('/invalid_template/') @pytest.mark.ignore_template_errors def test_ignore(client): client.get('/invalid_template/') """ ) result = django_testdir.runpytest_subprocess("-s") result.stdout.fnmatch_lines_random(["tpkg/test_the_test.py ..*"]) @pytest.mark.django_db def test_database_rollback() -> None: assert Item.objects.count() == 0 Item.objects.create(name="blah") assert Item.objects.count() == 1 @pytest.mark.django_db def test_database_rollback_again() -> None: test_database_rollback() @pytest.mark.django_db def test_database_name() -> None: dirname, name = os.path.split(connection.settings_dict["NAME"]) assert "file:memorydb" in name or name == ":memory:" or name.startswith("test_") def test_database_noaccess() -> None: with pytest.raises(RuntimeError): Item.objects.count() class TestrunnerVerbosity: """Test that Django's code to setup and teardown the databases uses pytest's verbosity level.""" @pytest.fixture def testdir(self, django_testdir): print("testdir") django_testdir.create_test_module( """ import pytest @pytest.mark.django_db def test_inner_testrunner(): pass """ ) return django_testdir def test_default(self, testdir) -> None: """Not verbose by default.""" result = testdir.runpytest_subprocess("-s") result.stdout.fnmatch_lines(["tpkg/test_the_test.py .*"]) def test_vq_verbosity_0(self, testdir) -> None: """-v and -q results in verbosity 0.""" result = testdir.runpytest_subprocess("-s", "-v", "-q") result.stdout.fnmatch_lines(["tpkg/test_the_test.py .*"]) def test_verbose_with_v(self, testdir) -> None: """Verbose output with '-v'.""" result = testdir.runpytest_subprocess("-s", "-v") result.stdout.fnmatch_lines_random(["tpkg/test_the_test.py:*", "*PASSED*"]) result.stderr.fnmatch_lines( ["*Destroying test database for alias 'default'*"] ) def test_more_verbose_with_vv(self, testdir) -> None: """More verbose output with '-v -v'.""" result = testdir.runpytest_subprocess("-s", "-v", "-v") result.stdout.fnmatch_lines_random( [ "tpkg/test_the_test.py:*", "*Operations to perform:*", "*Apply all migrations:*", "*PASSED*", ] ) result.stderr.fnmatch_lines( [ "*Creating test database for alias*", "*Destroying test database for alias 'default'*", ] ) def test_more_verbose_with_vv_and_reusedb(self, testdir) -> None: """More verbose output with '-v -v', and --create-db.""" result = testdir.runpytest_subprocess("-s", "-v", "-v", "--create-db") result.stdout.fnmatch_lines(["tpkg/test_the_test.py:*", "*PASSED*"]) result.stderr.fnmatch_lines(["*Creating test database for alias*"]) assert ( "*Destroying test database for alias 'default' ('*')...*" not in result.stderr.str() ) @pytest.mark.django_db @pytest.mark.parametrize("site_name", ["site1", "site2"]) def test_clear_site_cache(site_name: str, rf, monkeypatch) -> None: request = rf.get("/") monkeypatch.setattr(request, "get_host", lambda: "foo.com") Site.objects.create(domain="foo.com", name=site_name) assert Site.objects.get_current(request=request).name == site_name @pytest.mark.django_db @pytest.mark.parametrize("site_name", ["site1", "site2"]) def test_clear_site_cache_check_site_cache_size(site_name: str, settings) -> None: assert len(site_models.SITE_CACHE) == 0 site = Site.objects.create(domain="foo.com", name=site_name) settings.SITE_ID = site.id assert Site.objects.get_current() == site assert len(site_models.SITE_CACHE) == 1 pytest-django-4.5.2/tests/test_fixtures.py000066400000000000000000000671561415366732400207700ustar00rootroot00000000000000"""Tests for user-visible fixtures. Not quite all fixtures are tested here, the db and transactional_db fixtures are tested in test_database. """ import socket from contextlib import contextmanager from typing import Generator from urllib.error import HTTPError from urllib.request import urlopen import pytest from django.conf import settings as real_settings from django.core import mail from django.db import connection, transaction from django.test.client import Client, RequestFactory from django.utils.encoding import force_str from pytest_django.lazy_django import get_django_version from pytest_django_test.app.models import Item @contextmanager def nonverbose_config(config) -> Generator[None, None, None]: """Ensure that pytest's config.option.verbose is <= 0.""" if config.option.verbose <= 0: yield else: saved = config.option.verbose config.option.verbose = 0 yield config.option.verbose = saved def test_client(client) -> None: assert isinstance(client, Client) @pytest.mark.skipif(get_django_version() < (3, 1), reason="Django >= 3.1 required") def test_async_client(async_client) -> None: from django.test.client import AsyncClient assert isinstance(async_client, AsyncClient) @pytest.mark.django_db def test_admin_client(admin_client: Client) -> None: assert isinstance(admin_client, Client) resp = admin_client.get("/admin-required/") assert force_str(resp.content) == "You are an admin" def test_admin_client_no_db_marker(admin_client: Client) -> None: assert isinstance(admin_client, Client) resp = admin_client.get("/admin-required/") assert force_str(resp.content) == "You are an admin" # For test below. @pytest.fixture def existing_admin_user(django_user_model): return django_user_model._default_manager.create_superuser('admin', None, None) def test_admin_client_existing_user( db: None, existing_admin_user, admin_user, admin_client: Client, ) -> None: resp = admin_client.get("/admin-required/") assert force_str(resp.content) == "You are an admin" @pytest.mark.django_db def test_admin_user(admin_user, django_user_model) -> None: assert isinstance(admin_user, django_user_model) def test_admin_user_no_db_marker(admin_user, django_user_model) -> None: assert isinstance(admin_user, django_user_model) def test_rf(rf) -> None: assert isinstance(rf, RequestFactory) @pytest.mark.skipif(get_django_version() < (3, 1), reason="Django >= 3.1 required") def test_async_rf(async_rf) -> None: from django.test.client import AsyncRequestFactory assert isinstance(async_rf, AsyncRequestFactory) @pytest.mark.django_db def test_django_assert_num_queries_db(request, django_assert_num_queries) -> None: with nonverbose_config(request.config): with django_assert_num_queries(3): Item.objects.create(name="foo") Item.objects.create(name="bar") Item.objects.create(name="baz") with pytest.raises(pytest.fail.Exception) as excinfo: with django_assert_num_queries(2) as captured: Item.objects.create(name="quux") assert excinfo.value.args == ( "Expected to perform 2 queries but 1 was done " "(add -v option to show queries)", ) assert len(captured.captured_queries) == 1 @pytest.mark.django_db def test_django_assert_max_num_queries_db(request, django_assert_max_num_queries) -> None: with nonverbose_config(request.config): with django_assert_max_num_queries(2): Item.objects.create(name="1-foo") Item.objects.create(name="2-bar") with pytest.raises(pytest.fail.Exception) as excinfo: with django_assert_max_num_queries(2) as captured: Item.objects.create(name="1-foo") Item.objects.create(name="2-bar") Item.objects.create(name="3-quux") assert excinfo.value.args == ( "Expected to perform 2 queries or less but 3 were done " "(add -v option to show queries)", ) assert len(captured.captured_queries) == 3 assert "1-foo" in captured.captured_queries[0]["sql"] @pytest.mark.django_db(transaction=True) def test_django_assert_num_queries_transactional_db( request, transactional_db: None, django_assert_num_queries ) -> None: with nonverbose_config(request.config): with transaction.atomic(): with django_assert_num_queries(3): Item.objects.create(name="foo") Item.objects.create(name="bar") Item.objects.create(name="baz") with pytest.raises(pytest.fail.Exception): with django_assert_num_queries(2): Item.objects.create(name="quux") def test_django_assert_num_queries_output(django_testdir) -> None: django_testdir.create_test_module( """ from django.contrib.contenttypes.models import ContentType import pytest @pytest.mark.django_db def test_queries(django_assert_num_queries): with django_assert_num_queries(1): list(ContentType.objects.all()) ContentType.objects.count() """ ) result = django_testdir.runpytest_subprocess("--tb=short") result.stdout.fnmatch_lines(["*Expected to perform 1 queries but 2 were done*"]) assert result.ret == 1 def test_django_assert_num_queries_output_verbose(django_testdir) -> None: django_testdir.create_test_module( """ from django.contrib.contenttypes.models import ContentType import pytest @pytest.mark.django_db def test_queries(django_assert_num_queries): with django_assert_num_queries(11): list(ContentType.objects.all()) ContentType.objects.count() """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-v") result.stdout.fnmatch_lines( ["*Expected to perform 11 queries but 2 were done*", "*Queries:*", "*========*"] ) assert result.ret == 1 @pytest.mark.django_db def test_django_assert_num_queries_db_connection(django_assert_num_queries) -> None: from django.db import connection with django_assert_num_queries(1, connection=connection): Item.objects.create(name="foo") with django_assert_num_queries(1, connection=None): Item.objects.create(name="foo") with pytest.raises(AttributeError): with django_assert_num_queries(1, connection=False): pass @pytest.mark.django_db def test_django_assert_num_queries_output_info(django_testdir) -> None: django_testdir.create_test_module( """ from django.contrib.contenttypes.models import ContentType import pytest @pytest.mark.django_db def test_queries(django_assert_num_queries): with django_assert_num_queries( num=2, info="Expected: 1 for select all, 1 for count" ): list(ContentType.objects.all()) ContentType.objects.count() ContentType.objects.first() # additional wrong query """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-v") result.stdout.fnmatch_lines( [ "*Expected to perform 2 queries but 3 were done*", "*Expected: 1 for select all, 1 for count*", "*Queries:*", "*========*", ] ) assert result.ret == 1 @pytest.mark.django_db def test_django_capture_on_commit_callbacks(django_capture_on_commit_callbacks) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") scratch = [] with django_capture_on_commit_callbacks() as callbacks: transaction.on_commit(lambda: scratch.append("one")) assert len(callbacks) == 1 assert scratch == [] callbacks[0]() assert scratch == ["one"] scratch = [] with django_capture_on_commit_callbacks(execute=True) as callbacks: transaction.on_commit(lambda: scratch.append("two")) transaction.on_commit(lambda: scratch.append("three")) assert len(callbacks) == 2 assert scratch == ["two", "three"] callbacks[0]() assert scratch == ["two", "three", "two"] @pytest.mark.django_db(databases=["default", "second"]) def test_django_capture_on_commit_callbacks_multidb(django_capture_on_commit_callbacks) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") scratch = [] with django_capture_on_commit_callbacks(using="default", execute=True) as callbacks: transaction.on_commit(lambda: scratch.append("one")) assert len(callbacks) == 1 assert scratch == ["one"] scratch = [] with django_capture_on_commit_callbacks(using="second", execute=True) as callbacks: transaction.on_commit(lambda: scratch.append("two")) # pragma: no cover assert len(callbacks) == 0 assert scratch == [] scratch = [] with django_capture_on_commit_callbacks(using="default", execute=True) as callbacks: transaction.on_commit(lambda: scratch.append("ten")) transaction.on_commit(lambda: scratch.append("twenty"), using="second") # pragma: no cover transaction.on_commit(lambda: scratch.append("thirty")) assert len(callbacks) == 2 assert scratch == ["ten", "thirty"] @pytest.mark.django_db(transaction=True) def test_django_capture_on_commit_callbacks_transactional( django_capture_on_commit_callbacks, ) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") # Bad usage: no transaction (executes immediately). scratch = [] with django_capture_on_commit_callbacks() as callbacks: transaction.on_commit(lambda: scratch.append("one")) assert len(callbacks) == 0 assert scratch == ["one"] class TestSettings: """Tests for the settings fixture, order matters""" def test_modify_existing(self, settings) -> None: assert settings.SECRET_KEY == "foobar" assert real_settings.SECRET_KEY == "foobar" settings.SECRET_KEY = "spam" assert settings.SECRET_KEY == "spam" assert real_settings.SECRET_KEY == "spam" def test_modify_existing_again(self, settings) -> None: assert settings.SECRET_KEY == "foobar" assert real_settings.SECRET_KEY == "foobar" def test_new(self, settings) -> None: assert not hasattr(settings, "SPAM") assert not hasattr(real_settings, "SPAM") settings.SPAM = "ham" assert settings.SPAM == "ham" assert real_settings.SPAM == "ham" def test_new_again(self, settings) -> None: assert not hasattr(settings, "SPAM") assert not hasattr(real_settings, "SPAM") def test_deleted(self, settings) -> None: assert hasattr(settings, "SECRET_KEY") assert hasattr(real_settings, "SECRET_KEY") del settings.SECRET_KEY assert not hasattr(settings, "SECRET_KEY") assert not hasattr(real_settings, "SECRET_KEY") def test_deleted_again(self, settings) -> None: assert hasattr(settings, "SECRET_KEY") assert hasattr(real_settings, "SECRET_KEY") def test_signals(self, settings) -> None: result = [] def assert_signal(signal, sender, setting, value, enter): result.append((setting, value, enter)) from django.test.signals import setting_changed setting_changed.connect(assert_signal) result = [] settings.SECRET_KEY = "change 1" settings.SECRET_KEY = "change 2" assert result == [ ("SECRET_KEY", "change 1", True), ("SECRET_KEY", "change 2", True), ] result = [] settings.FOOBAR = "abc123" assert sorted(result) == [("FOOBAR", "abc123", True)] def test_modification_signal(self, django_testdir) -> None: django_testdir.create_test_module( """ import pytest from django.conf import settings from django.test.signals import setting_changed @pytest.fixture(autouse=True, scope='session') def settings_change_printer(): def receiver(sender, **kwargs): fmt_dict = {'actual_value': getattr(settings, kwargs['setting'], '<>')} fmt_dict.update(kwargs) print('Setting changed: ' 'enter=%(enter)s,setting=%(setting)s,' 'value=%(value)s,actual_value=%(actual_value)s' % fmt_dict) setting_changed.connect(receiver, weak=False) def test_set(settings): settings.SECRET_KEY = 'change 1' settings.SECRET_KEY = 'change 2' def test_set_non_existent(settings): settings.FOOBAR = 'abc123' """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-v", "-s") # test_set result.stdout.fnmatch_lines( [ "*Setting changed: enter=True,setting=SECRET_KEY,value=change 1*", "*Setting changed: enter=True,setting=SECRET_KEY,value=change 2*", "*Setting changed: enter=False,setting=SECRET_KEY,value=change 1*", "*Setting changed: enter=False,setting=SECRET_KEY,value=foobar*", ] ) result.stdout.fnmatch_lines( [ "*Setting changed: enter=True,setting=FOOBAR,value=abc123*", ( "*Setting changed: enter=False,setting=FOOBAR,value=None," "actual_value=<>*" ), ] ) class TestLiveServer: def test_settings_before(self) -> None: from django.conf import settings assert ( "{}.{}".format(settings.__class__.__module__, settings.__class__.__name__) == "django.conf.Settings" ) TestLiveServer._test_settings_before_run = True # type: ignore[attr-defined] def test_url(self, live_server) -> None: assert live_server.url == force_str(live_server) def test_change_settings(self, live_server, settings) -> None: assert live_server.url == force_str(live_server) def test_settings_restored(self) -> None: """Ensure that settings are restored after test_settings_before.""" from django.conf import settings assert TestLiveServer._test_settings_before_run is True # type: ignore[attr-defined] assert ( "{}.{}".format(settings.__class__.__module__, settings.__class__.__name__) == "django.conf.Settings" ) assert settings.ALLOWED_HOSTS == ["testserver"] def test_transactions(self, live_server) -> None: if not connection.features.supports_transactions: pytest.skip("transactions required for this test") assert not connection.in_atomic_block def test_db_changes_visibility(self, live_server) -> None: response_data = urlopen(live_server + "/item_count/").read() assert force_str(response_data) == "Item count: 0" Item.objects.create(name="foo") response_data = urlopen(live_server + "/item_count/").read() assert force_str(response_data) == "Item count: 1" def test_fixture_db(self, db: None, live_server) -> None: Item.objects.create(name="foo") response_data = urlopen(live_server + "/item_count/").read() assert force_str(response_data) == "Item count: 1" def test_fixture_transactional_db(self, transactional_db: None, live_server) -> None: Item.objects.create(name="foo") response_data = urlopen(live_server + "/item_count/").read() assert force_str(response_data) == "Item count: 1" @pytest.fixture def item(self) -> None: # This has not requested database access explicitly, but the # live_server fixture auto-uses the transactional_db fixture. Item.objects.create(name="foo") def test_item(self, item, live_server) -> None: pass @pytest.fixture def item_db(self, db: None) -> Item: return Item.objects.create(name="foo") def test_item_db(self, item_db: Item, live_server) -> None: response_data = urlopen(live_server + "/item_count/").read() assert force_str(response_data) == "Item count: 1" @pytest.fixture def item_transactional_db(self, transactional_db: None) -> Item: return Item.objects.create(name="foo") def test_item_transactional_db(self, item_transactional_db: Item, live_server) -> None: response_data = urlopen(live_server + "/item_count/").read() assert force_str(response_data) == "Item count: 1" @pytest.mark.django_project( extra_settings=""" INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.staticfiles', 'tpkg.app', ] STATIC_URL = '/static/' """ ) def test_serve_static_with_staticfiles_app(self, django_testdir, settings) -> None: """ LiveServer always serves statics with ``django.contrib.staticfiles`` handler. """ django_testdir.create_test_module( """ from urllib.request import urlopen from django.utils.encoding import force_str class TestLiveServer: def test_a(self, live_server, settings): assert ('django.contrib.staticfiles' in settings.INSTALLED_APPS) response_data = urlopen( live_server + '/static/a_file.txt').read() assert force_str(response_data) == 'bla\\n' """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-v") result.stdout.fnmatch_lines(["*test_a*PASSED*"]) assert result.ret == 0 def test_serve_static_dj17_without_staticfiles_app(self, live_server, settings) -> None: """ Because ``django.contrib.staticfiles`` is not installed LiveServer can not serve statics with django >= 1.7 . """ with pytest.raises(HTTPError): urlopen(live_server + "/static/a_file.txt").read() def test_specified_port_django_111(self, django_testdir) -> None: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.bind(("", 0)) __, port = sock.getsockname() finally: sock.close() django_testdir.create_test_module( """ def test_with_live_server(live_server): assert live_server.port == %d """ % port ) django_testdir.runpytest_subprocess("--liveserver=localhost:%s" % port) @pytest.mark.parametrize("username_field", ("email", "identifier")) @pytest.mark.django_project( extra_settings=""" AUTH_USER_MODEL = 'app.MyCustomUser' INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'tpkg.app', ] ROOT_URLCONF = 'tpkg.app.urls' """ ) def test_custom_user_model(django_testdir, username_field) -> None: django_testdir.create_app_file( """ from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.db import models class MyCustomUserManager(BaseUserManager): def create_user(self, {username_field}, password=None, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) user = self.model({username_field}={username_field}, **extra_fields) user.set_password(password) user.save() return user def create_superuser(self, {username_field}, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) return self.create_user( {username_field}={username_field}, password=password, **extra_fields ) class MyCustomUser(AbstractBaseUser, PermissionsMixin): email = models.EmailField(max_length=100, unique=True) identifier = models.CharField(unique=True, max_length=100) is_staff = models.BooleanField( 'staff status', default=False, help_text='Designates whether the user can log into this admin site.' ) objects = MyCustomUserManager() USERNAME_FIELD = '{username_field}' """.format(username_field=username_field), "models.py", ) django_testdir.create_app_file( """ from django.urls import path from tpkg.app import views urlpatterns = [path('admin-required/', views.admin_required_view)] """, "urls.py", ) django_testdir.create_app_file( """ from django.http import HttpResponse from django.template import Template from django.template.context import Context def admin_required_view(request): assert request.user.is_staff return HttpResponse(Template('You are an admin').render(Context())) """, "views.py", ) django_testdir.makepyfile( """ from django.utils.encoding import force_str from tpkg.app.models import MyCustomUser def test_custom_user_model(admin_client): resp = admin_client.get('/admin-required/') assert force_str(resp.content) == 'You are an admin' """ ) django_testdir.create_app_file("", "migrations/__init__.py") django_testdir.create_app_file( """ from django.db import models, migrations import django.utils.timezone import django.core.validators class Migration(migrations.Migration): dependencies = [ ('auth', '0001_initial'), ('app', '0001_initial'), ] operations = [ migrations.CreateModel( name='MyCustomUser', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('email', models.EmailField(error_messages={'unique': 'A user with that email address already exists.'}, max_length=100, unique=True, verbose_name='email address')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('identifier', models.CharField(unique=True, max_length=100)), ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups')), ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')), ], options={ 'verbose_name': 'user', 'verbose_name_plural': 'users', }, bases=None, ), ] """, # noqa: E501 "migrations/0002_custom_user_model.py", ) result = django_testdir.runpytest_subprocess("-s") result.stdout.fnmatch_lines(["* 1 passed*"]) assert result.ret == 0 class Test_django_db_blocker: @pytest.mark.django_db def test_block_manually(self, django_db_blocker) -> None: try: django_db_blocker.block() with pytest.raises(RuntimeError): Item.objects.exists() finally: django_db_blocker.restore() @pytest.mark.django_db def test_block_with_block(self, django_db_blocker) -> None: with django_db_blocker.block(): with pytest.raises(RuntimeError): Item.objects.exists() def test_unblock_manually(self, django_db_blocker) -> None: try: django_db_blocker.unblock() Item.objects.exists() finally: django_db_blocker.restore() def test_unblock_with_block(self, django_db_blocker) -> None: with django_db_blocker.unblock(): Item.objects.exists() def test_mail(mailoutbox) -> None: assert ( mailoutbox is mail.outbox ) # check that mail.outbox and fixture value is same object assert len(mailoutbox) == 0 mail.send_mail("subject", "body", "from@example.com", ["to@example.com"]) assert len(mailoutbox) == 1 m = mailoutbox[0] assert m.subject == "subject" assert m.body == "body" assert m.from_email == "from@example.com" assert list(m.to) == ["to@example.com"] def test_mail_again(mailoutbox) -> None: test_mail(mailoutbox) def test_mail_message_uses_mocked_DNS_NAME(mailoutbox) -> None: mail.send_mail("subject", "body", "from@example.com", ["to@example.com"]) m = mailoutbox[0] message = m.message() assert message["Message-ID"].endswith("@fake-tests.example.com>") def test_mail_message_uses_django_mail_dnsname_fixture(django_testdir) -> None: django_testdir.create_test_module( """ from django.core import mail import pytest @pytest.fixture def django_mail_dnsname(): return 'from.django_mail_dnsname' def test_mailbox_inner(mailoutbox): mail.send_mail('subject', 'body', 'from@example.com', ['to@example.com']) m = mailoutbox[0] message = m.message() assert message['Message-ID'].endswith('@from.django_mail_dnsname>') """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-v") result.stdout.fnmatch_lines(["*test_mailbox_inner*PASSED*"]) assert result.ret == 0 def test_mail_message_dns_patching_can_be_skipped(django_testdir) -> None: django_testdir.create_test_module( """ from django.core import mail import pytest @pytest.fixture def django_mail_dnsname(): raise Exception('should not get called') @pytest.fixture def django_mail_patch_dns(): print('\\ndjango_mail_dnsname_mark') def test_mailbox_inner(mailoutbox, monkeypatch): def mocked_make_msgid(*args, **kwargs): mocked_make_msgid.called += [(args, kwargs)] mocked_make_msgid.called = [] monkeypatch.setattr(mail.message, 'make_msgid', mocked_make_msgid) mail.send_mail('subject', 'body', 'from@example.com', ['to@example.com']) m = mailoutbox[0] assert len(mocked_make_msgid.called) == 1 assert mocked_make_msgid.called[0][1]['domain'] is mail.DNS_NAME """ ) result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-s") result.stdout.fnmatch_lines( ["*test_mailbox_inner*", "django_mail_dnsname_mark", "PASSED*"] ) assert result.ret == 0 pytest-django-4.5.2/tests/test_initialization.py000066400000000000000000000032221415366732400221260ustar00rootroot00000000000000from textwrap import dedent def test_django_setup_order_and_uniqueness(django_testdir, monkeypatch) -> None: """ The django.setup() function shall not be called multiple times by pytest-django, since it resets logging conf each time. """ django_testdir.makeconftest( """ import django.apps assert django.apps.apps.ready from tpkg.app.models import Item print("conftest") def pytest_configure(): import django print("pytest_configure: conftest") django.setup = lambda: SHOULD_NOT_GET_CALLED """ ) django_testdir.project_root.join("tpkg", "plugin.py").write( dedent( """ import pytest import django.apps assert not django.apps.apps.ready print("plugin") def pytest_configure(): assert django.apps.apps.ready from tpkg.app.models import Item print("pytest_configure: plugin") @pytest.hookimpl(tryfirst=True) def pytest_load_initial_conftests(early_config, parser, args): print("pytest_load_initial_conftests") assert not django.apps.apps.ready """ ) ) django_testdir.makepyfile( """ def test_ds(): pass """ ) result = django_testdir.runpytest_subprocess("-s", "-p", "tpkg.plugin") result.stdout.fnmatch_lines( [ "plugin", "pytest_load_initial_conftests", "conftest", "pytest_configure: conftest", "pytest_configure: plugin", "* 1 passed*", ] ) assert result.ret == 0 pytest-django-4.5.2/tests/test_manage_py_scan.py000066400000000000000000000126731415366732400220550ustar00rootroot00000000000000import pytest @pytest.mark.django_project(project_root="django_project_root", create_manage_py=True) def test_django_project_found(django_testdir) -> None: # XXX: Important: Do not chdir() to django_project_root since runpytest_subprocess # will call "python /path/to/pytest.py", which will implicitly add cwd to # the path. By instead calling "python /path/to/pytest.py # django_project_root", we avoid implicitly adding the project to sys.path # This matches the behaviour when pytest is called directly as an # executable (cwd is not added to the Python path) django_testdir.create_test_module( """ def test_foobar(): assert 1 + 1 == 2 """ ) result = django_testdir.runpytest_subprocess("django_project_root") assert result.ret == 0 outcomes = result.parseoutcomes() assert outcomes["passed"] == 1 @pytest.mark.django_project(project_root="django_project_root", create_manage_py=True) def test_django_project_found_with_k(django_testdir, monkeypatch) -> None: """Test that cwd is checked as fallback with non-args via '-k foo'.""" testfile = django_testdir.create_test_module( """ def test_foobar(): assert True """, "sub/test_in_sub.py", ) monkeypatch.chdir(testfile.dirname) result = django_testdir.runpytest_subprocess("-k", "test_foobar") assert result.ret == 0 outcomes = result.parseoutcomes() assert outcomes["passed"] == 1 @pytest.mark.django_project(project_root="django_project_root", create_manage_py=True) def test_django_project_found_with_k_and_cwd(django_testdir, monkeypatch) -> None: """Cover cwd not used as fallback if present already in args.""" testfile = django_testdir.create_test_module( """ def test_foobar(): assert True """, "sub/test_in_sub.py", ) monkeypatch.chdir(testfile.dirname) result = django_testdir.runpytest_subprocess(testfile.dirname, "-k", "test_foobar") assert result.ret == 0 outcomes = result.parseoutcomes() assert outcomes["passed"] == 1 @pytest.mark.django_project(project_root="django_project_root", create_manage_py=True) def test_django_project_found_absolute(django_testdir, monkeypatch) -> None: """This only tests that "." is added as an absolute path (#637).""" django_testdir.create_test_module( """ def test_dot_not_in_syspath(): import sys assert '.' not in sys.path[:5] """ ) monkeypatch.chdir("django_project_root") # NOTE: the "." here is important to test for an absolute path being used. result = django_testdir.runpytest_subprocess("-s", ".") assert result.ret == 0 outcomes = result.parseoutcomes() assert outcomes["passed"] == 1 @pytest.mark.django_project(project_root="django_project_root", create_manage_py=True) def test_django_project_found_invalid_settings(django_testdir, monkeypatch) -> None: monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "DOES_NOT_EXIST") result = django_testdir.runpytest_subprocess("django_project_root") assert result.ret != 0 result.stderr.fnmatch_lines(["*ImportError:*DOES_NOT_EXIST*"]) result.stderr.fnmatch_lines(["*pytest-django found a Django project*"]) def test_django_project_scan_disabled_invalid_settings(django_testdir, monkeypatch) -> None: monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "DOES_NOT_EXIST") django_testdir.makeini( """ [pytest] django_find_project = false """ ) result = django_testdir.runpytest_subprocess("django_project_root") assert result.ret != 0 result.stderr.fnmatch_lines(["*ImportError*DOES_NOT_EXIST*"]) result.stderr.fnmatch_lines( ["*pytest-django did not search for " "Django projects*"] ) @pytest.mark.django_project(project_root="django_project_root", create_manage_py=True) def test_django_project_found_invalid_settings_version(django_testdir, monkeypatch) -> None: """Invalid DSM should not cause an error with --help or --version.""" monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "DOES_NOT_EXIST") result = django_testdir.runpytest_subprocess("django_project_root", "--version", "--version") assert result.ret == 0 result.stderr.fnmatch_lines(["*This is pytest version*"]) result = django_testdir.runpytest_subprocess("django_project_root", "--help") assert result.ret == 0 result.stdout.fnmatch_lines(["*usage:*"]) @pytest.mark.django_project(project_root="django_project_root", create_manage_py=True) def test_runs_without_error_on_long_args(django_testdir) -> None: django_testdir.create_test_module( """ def test_this_is_a_long_message_which_caused_a_bug_when_scanning_for_manage_py_12346712341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234112341234112451234123412341234123412341234123412341234123412341234123412341234123412341234123412341234(): assert 1 + 1 == 2 """ # noqa: E501 ) result = django_testdir.runpytest_subprocess( "-k", "this_is_a_long_message_which_caused_a_bug_when_scanning_for_manage_py_12346712341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234112341234112451234123412341234123412341234123412341234123412341234123412341234123412341234123412341234", # noqa: E501 "django_project_root", ) assert result.ret == 0 pytest-django-4.5.2/tests/test_unittest.py000066400000000000000000000332731415366732400207670ustar00rootroot00000000000000import pytest from django.test import TestCase from pytest_django_test.app.models import Item class TestFixtures(TestCase): fixtures = ["items"] def test_fixtures(self) -> None: assert Item.objects.count() == 1 assert Item.objects.get().name == "Fixture item" def test_fixtures_again(self) -> None: """Ensure fixtures are only loaded once.""" self.test_fixtures() class TestSetup(TestCase): def setUp(self) -> None: """setUp should be called after starting a transaction""" assert Item.objects.count() == 0 Item.objects.create(name="Some item") Item.objects.create(name="Some item again") def test_count(self) -> None: self.assertEqual(Item.objects.count(), 2) assert Item.objects.count() == 2 Item.objects.create(name="Foo") self.assertEqual(Item.objects.count(), 3) def test_count_again(self) -> None: self.test_count() def tearDown(self) -> None: """tearDown should be called before rolling back the database""" assert Item.objects.count() == 3 class TestFixturesWithSetup(TestCase): fixtures = ["items"] def setUp(self) -> None: assert Item.objects.count() == 1 Item.objects.create(name="Some item") def test_count(self) -> None: assert Item.objects.count() == 2 Item.objects.create(name="Some item again") def test_count_again(self) -> None: self.test_count() def tearDown(self) -> None: assert Item.objects.count() == 3 def test_sole_test(django_testdir) -> None: """ Make sure the database is configured when only Django TestCase classes are collected, without the django_db marker. Also ensures that the DB is available after a failure (#824). """ django_testdir.create_test_module( """ import os from django.test import TestCase from django.conf import settings from .app.models import Item class TestFoo(TestCase): def test_foo(self): # Make sure we are actually using the test database _, db_name = os.path.split(settings.DATABASES['default']['NAME']) assert db_name.startswith('test_') or db_name == ':memory:' \\ or 'file:memorydb' in db_name # Make sure it is usable assert Item.objects.count() == 0 assert 0, "trigger_error" class TestBar(TestCase): def test_bar(self): assert Item.objects.count() == 0 """ ) result = django_testdir.runpytest_subprocess("-v") result.stdout.fnmatch_lines( [ "*::test_foo FAILED", "*::test_bar PASSED", '> assert 0, "trigger_error"', "E AssertionError: trigger_error", "E assert 0", "*= 1 failed, 1 passed*", ] ) assert result.ret == 1 class TestUnittestMethods: "Test that setup/teardown methods of unittests are being called." def test_django(self, django_testdir) -> None: django_testdir.create_test_module( """ from django.test import TestCase class TestFoo(TestCase): @classmethod def setUpClass(self): print('\\nCALLED: setUpClass') def setUp(self): print('\\nCALLED: setUp') def tearDown(self): print('\\nCALLED: tearDown') @classmethod def tearDownClass(self): print('\\nCALLED: tearDownClass') def test_pass(self): pass """ ) result = django_testdir.runpytest_subprocess("-v", "-s") result.stdout.fnmatch_lines( [ "CALLED: setUpClass", "CALLED: setUp", "CALLED: tearDown", "PASSED*", "CALLED: tearDownClass", ] ) assert result.ret == 0 def test_setUpClass_not_being_a_classmethod(self, django_testdir) -> None: django_testdir.create_test_module( """ from django.test import TestCase class TestFoo(TestCase): def setUpClass(self): pass def test_pass(self): pass """ ) result = django_testdir.runpytest_subprocess("-v", "-s") expected_lines = [ "* ERROR at setup of TestFoo.test_pass *", "E * TypeError: *", ] result.stdout.fnmatch_lines(expected_lines) assert result.ret == 1 def test_setUpClass_multiple_subclasses(self, django_testdir) -> None: django_testdir.create_test_module( """ from django.test import TestCase class TestFoo(TestCase): @classmethod def setUpClass(cls): super(TestFoo, cls).setUpClass() def test_shared(self): pass class TestBar(TestFoo): def test_bar1(self): pass class TestBar2(TestFoo): def test_bar21(self): pass """ ) result = django_testdir.runpytest_subprocess("-v") result.stdout.fnmatch_lines( [ "*TestFoo::test_shared PASSED*", "*TestBar::test_bar1 PASSED*", "*TestBar::test_shared PASSED*", "*TestBar2::test_bar21 PASSED*", "*TestBar2::test_shared PASSED*", ] ) assert result.ret == 0 def test_setUpClass_mixin(self, django_testdir) -> None: django_testdir.create_test_module( """ from django.test import TestCase class TheMixin: @classmethod def setUpClass(cls): super(TheMixin, cls).setUpClass() class TestFoo(TheMixin, TestCase): def test_foo(self): pass class TestBar(TheMixin, TestCase): def test_bar(self): pass """ ) result = django_testdir.runpytest_subprocess("-v") result.stdout.fnmatch_lines( ["*TestFoo::test_foo PASSED*", "*TestBar::test_bar PASSED*"] ) assert result.ret == 0 def test_setUpClass_skip(self, django_testdir) -> None: django_testdir.create_test_module( """ from django.test import TestCase import pytest class TestFoo(TestCase): @classmethod def setUpClass(cls): if cls is TestFoo: raise pytest.skip("Skip base class") super(TestFoo, cls).setUpClass() def test_shared(self): pass class TestBar(TestFoo): def test_bar1(self): pass class TestBar2(TestFoo): def test_bar21(self): pass """ ) result = django_testdir.runpytest_subprocess("-v") result.stdout.fnmatch_lines( [ "*TestFoo::test_shared SKIPPED*", "*TestBar::test_bar1 PASSED*", "*TestBar::test_shared PASSED*", "*TestBar2::test_bar21 PASSED*", "*TestBar2::test_shared PASSED*", ] ) assert result.ret == 0 def test_multi_inheritance_setUpClass(self, django_testdir) -> None: django_testdir.create_test_module( """ from django.test import TestCase # Using a mixin is a regression test, see #280 for more details: # https://github.com/pytest-dev/pytest-django/issues/280 class SomeMixin: pass class TestA(SomeMixin, TestCase): expected_state = ['A'] state = [] @classmethod def setUpClass(cls): super(TestA, cls).setUpClass() cls.state.append('A') @classmethod def tearDownClass(cls): assert cls.state.pop() == 'A' super(TestA, cls).tearDownClass() def test_a(self): assert self.state == self.expected_state class TestB(TestA): expected_state = ['A', 'B'] @classmethod def setUpClass(cls): super(TestB, cls).setUpClass() cls.state.append('B') @classmethod def tearDownClass(cls): assert cls.state.pop() == 'B' super(TestB, cls).tearDownClass() def test_b(self): assert self.state == self.expected_state class TestC(TestB): expected_state = ['A', 'B', 'C'] @classmethod def setUpClass(cls): super(TestC, cls).setUpClass() cls.state.append('C') @classmethod def tearDownClass(cls): assert cls.state.pop() == 'C' super(TestC, cls).tearDownClass() def test_c(self): assert self.state == self.expected_state """ ) result = django_testdir.runpytest_subprocess("-vvvv", "-s") assert result.parseoutcomes()["passed"] == 6 assert result.ret == 0 def test_unittest(self, django_testdir) -> None: django_testdir.create_test_module( """ from unittest import TestCase class TestFoo(TestCase): @classmethod def setUpClass(self): print('\\nCALLED: setUpClass') def setUp(self): print('\\nCALLED: setUp') def tearDown(self): print('\\nCALLED: tearDown') @classmethod def tearDownClass(self): print('\\nCALLED: tearDownClass') def test_pass(self): pass """ ) result = django_testdir.runpytest_subprocess("-v", "-s") result.stdout.fnmatch_lines( [ "CALLED: setUpClass", "CALLED: setUp", "CALLED: tearDown", "PASSED*", "CALLED: tearDownClass", ] ) assert result.ret == 0 def test_setUpClass_leaf_but_not_in_dunder_dict(self, django_testdir) -> None: django_testdir.create_test_module( """ from django.test import testcases class CMSTestCase(testcases.TestCase): pass class FooBarTestCase(testcases.TestCase): @classmethod def setUpClass(cls): print('FooBarTestCase.setUpClass') super(FooBarTestCase, cls).setUpClass() class TestContact(CMSTestCase, FooBarTestCase): def test_noop(self): print('test_noop') """ ) result = django_testdir.runpytest_subprocess("-q", "-s") result.stdout.fnmatch_lines( ["*FooBarTestCase.setUpClass*", "*test_noop*", "1 passed*"] ) assert result.ret == 0 class TestCaseWithDbFixture(TestCase): pytestmark = pytest.mark.usefixtures("db") def test_simple(self) -> None: # We only want to check setup/teardown does not conflict assert 1 class TestCaseWithTrDbFixture(TestCase): pytestmark = pytest.mark.usefixtures("transactional_db") def test_simple(self) -> None: # We only want to check setup/teardown does not conflict assert 1 def test_pdb_enabled(django_testdir) -> None: """ Make sure the database is flushed and tests are isolated when using the --pdb option. See issue #405 for details: https://github.com/pytest-dev/pytest-django/issues/405 """ django_testdir.create_test_module( ''' import os from django.test import TestCase from django.conf import settings from .app.models import Item class TestPDBIsolation(TestCase): def setUp(self): """setUp should be called after starting a transaction""" assert Item.objects.count() == 0 Item.objects.create(name='Some item') Item.objects.create(name='Some item again') def test_count(self): self.assertEqual(Item.objects.count(), 2) assert Item.objects.count() == 2 Item.objects.create(name='Foo') self.assertEqual(Item.objects.count(), 3) def test_count_again(self): self.test_count() def tearDown(self): """tearDown should be called before rolling back the database""" assert Item.objects.count() == 3 ''' ) result = django_testdir.runpytest_subprocess("-v", "--pdb") assert result.ret == 0 def test_debug_not_used(django_testdir) -> None: django_testdir.create_test_module( """ from django.test import TestCase pre_setup_count = 0 class TestClass1(TestCase): def debug(self): assert 0, "should not be called" def test_method(self): pass """ ) result = django_testdir.runpytest_subprocess("--pdb") result.stdout.fnmatch_lines(["*= 1 passed*"]) assert result.ret == 0 pytest-django-4.5.2/tests/test_urls.py000066400000000000000000000042271415366732400200720ustar00rootroot00000000000000import pytest from django.conf import settings from django.urls import is_valid_path from django.utils.encoding import force_str @pytest.mark.urls("pytest_django_test.urls_overridden") def test_urls() -> None: assert settings.ROOT_URLCONF == "pytest_django_test.urls_overridden" assert is_valid_path("/overridden_url/") @pytest.mark.urls("pytest_django_test.urls_overridden") def test_urls_client(client) -> None: response = client.get("/overridden_url/") assert force_str(response.content) == "Overridden urlconf works!" def test_urls_cache_is_cleared(testdir) -> None: testdir.makepyfile( myurls=""" from django.urls import path def fake_view(request): pass urlpatterns = [path('first', fake_view, name='first')] """ ) testdir.makepyfile( """ from django.urls import reverse, NoReverseMatch import pytest @pytest.mark.urls('myurls') def test_something(): reverse('first') def test_something_else(): with pytest.raises(NoReverseMatch): reverse('first') """ ) result = testdir.runpytest_subprocess() assert result.ret == 0 def test_urls_cache_is_cleared_and_new_urls_can_be_assigned(testdir) -> None: testdir.makepyfile( myurls=""" from django.urls import path def fake_view(request): pass urlpatterns = [path('first', fake_view, name='first')] """ ) testdir.makepyfile( myurls2=""" from django.urls import path def fake_view(request): pass urlpatterns = [path('second', fake_view, name='second')] """ ) testdir.makepyfile( """ from django.urls import reverse, NoReverseMatch import pytest @pytest.mark.urls('myurls') def test_something(): reverse('first') @pytest.mark.urls('myurls2') def test_something_else(): with pytest.raises(NoReverseMatch): reverse('first') reverse('second') """ ) result = testdir.runpytest_subprocess() assert result.ret == 0 pytest-django-4.5.2/tests/test_without_django_loaded.py000066400000000000000000000045651415366732400234470ustar00rootroot00000000000000import pytest @pytest.fixture def no_ds(monkeypatch) -> None: """Ensure DJANGO_SETTINGS_MODULE is unset""" monkeypatch.delenv("DJANGO_SETTINGS_MODULE") pytestmark = pytest.mark.usefixtures("no_ds") def test_no_ds(testdir) -> None: testdir.makepyfile( """ import os def test_env(): assert 'DJANGO_SETTINGS_MODULE' not in os.environ def test_cfg(pytestconfig): assert pytestconfig.option.ds is None """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 def test_database(testdir) -> None: testdir.makepyfile( """ import pytest @pytest.mark.django_db def test_mark(): assert 0 @pytest.mark.django_db(transaction=True) def test_mark_trans(): assert 0 def test_db(db): assert 0 def test_transactional_db(transactional_db): assert 0 """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 r.stdout.fnmatch_lines(["*4 skipped*"]) def test_client(testdir) -> None: testdir.makepyfile( """ def test_client(client): assert 0 def test_admin_client(admin_client): assert 0 """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 r.stdout.fnmatch_lines(["*2 skipped*"]) def test_rf(testdir) -> None: testdir.makepyfile( """ def test_rf(rf): assert 0 """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 r.stdout.fnmatch_lines(["*1 skipped*"]) def test_settings(testdir) -> None: testdir.makepyfile( """ def test_settings(settings): assert 0 """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 r.stdout.fnmatch_lines(["*1 skipped*"]) def test_live_server(testdir) -> None: testdir.makepyfile( """ def test_live_server(live_server): assert 0 """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 r.stdout.fnmatch_lines(["*1 skipped*"]) def test_urls_mark(testdir) -> None: testdir.makepyfile( """ import pytest @pytest.mark.urls('foo.bar') def test_urls(): assert 0 """ ) r = testdir.runpytest_subprocess() assert r.ret == 0 r.stdout.fnmatch_lines(["*1 skipped*"]) pytest-django-4.5.2/tox.ini000066400000000000000000000044101415366732400156370ustar00rootroot00000000000000[tox] envlist = py310-dj{main,40,32}-postgres py39-dj{main,40,32,31,22}-postgres py38-dj{main,40,32,31,22}-postgres py37-dj{32,31,22}-postgres py36-dj{32,31,22}-postgres py35-dj{22}-postgres linting [testenv] extras = testing deps = djmain: https://github.com/django/django/archive/main.tar.gz dj40: Django>=4.0,<4.1 dj32: Django>=3.2,<4.0 dj31: Django>=3.1,<3.2 dj22: Django>=2.2,<2.3 mysql_myisam: mysqlclient==2.1.0 mysql_innodb: mysqlclient==2.1.0 !pypy3-postgres: psycopg2-binary pypy3-postgres: psycopg2cffi coverage: coverage-enable-subprocess pytest54: pytest>=5.4,<5.5 xdist: pytest-xdist>=1.15 setenv = PYTHONPATH = {toxinidir}:{env:PYTHONPATH:} mysql_innodb: DJANGO_SETTINGS_MODULE=pytest_django_test.settings_mysql_innodb mysql_myisam: DJANGO_SETTINGS_MODULE=pytest_django_test.settings_mysql_myisam postgres: DJANGO_SETTINGS_MODULE=pytest_django_test.settings_postgres sqlite: DJANGO_SETTINGS_MODULE=pytest_django_test.settings_sqlite sqlite_file: DJANGO_SETTINGS_MODULE=pytest_django_test.settings_sqlite_file coverage: PYTESTDJANGO_TEST_RUNNER=coverage run -m pytest coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc coverage: COVERAGE_FILE={toxinidir}/.coverage coverage: PYTESTDJANGO_COVERAGE_SRC={toxinidir}/ passenv = PYTEST_ADDOPTS TERM TEST_DB_USER TEST_DB_PASSWORD TEST_DB_HOST usedevelop = True commands = coverage: coverage erase {env:PYTESTDJANGO_TEST_RUNNER:pytest} {posargs:tests} coverage: coverage combine coverage: coverage report coverage: coverage xml [testenv:linting] extras = deps = flake8 mypy==0.910 isort commands = flake8 --version flake8 --statistics {posargs:pytest_django pytest_django_test tests} mypy {posargs:pytest_django pytest_django_test tests} isort --check-only --diff pytest_django pytest_django_test tests [testenv:doc8] extras = basepython = python3.8 skip_install = true deps = sphinx doc8 commands = doc8 docs/ [testenv:docs] deps = extras = docs commands = sphinx-build -n -W -b html -d docs/_build/doctrees docs docs/_build/html [testenv:readme] extras = basepython = python3.8 deps = readme_renderer commands = python setup.py check -r -s