├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── deploy.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── RELEASING.rst ├── docs ├── Makefile ├── changelog.rst ├── conf.py ├── contributing.rst ├── features.rst ├── index.rst ├── make.bat └── tutorial.rst ├── pyproject.toml ├── requirements ├── docs.txt ├── main.txt └── test.txt ├── setup.cfg ├── setup.py ├── src └── pytest_flask │ ├── __init__.py │ ├── _internal.py │ ├── fixtures.py │ ├── live_server.py │ ├── plugin.py │ ├── py.typed │ └── pytest_compat.py ├── tests ├── conftest.py ├── test_fixtures.py ├── test_internal.py ├── test_json_response.py ├── test_live_server.py ├── test_markers.py └── test_response_overwriting.py └── tox.ini /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | - OS: [e.g. macOS Catalina] 28 | - Python Version [e.g. 3.8.5] 29 | - pytest-flask version [e.g. 0.15.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | - fixes # 12 | 13 | 18 | 19 | Checklist: 20 | 21 | - [ ] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change. 22 | - [ ] Add or update relevant docs, in the docs folder and in code. 23 | - [ ] Add an entry in `docs/CHANGELOG.rst`, summarizing the change and linking to the issue. 24 | - [ ] Run `pre-commit` hooks and fix any issues. 25 | - [ ] Run `pytest` and make sure no tests failed. 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Release version' 8 | required: true 9 | default: '1.2.3' 10 | 11 | jobs: 12 | 13 | package: 14 | runs-on: ubuntu-latest 15 | env: 16 | SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Build and Check Package 22 | uses: hynek/build-and-inspect-python-package@v1.5 23 | 24 | deploy: 25 | needs: package 26 | runs-on: ubuntu-latest 27 | environment: deploy 28 | permissions: 29 | id-token: write # For PyPI trusted publishers. 30 | contents: write # For tag and release notes. 31 | 32 | steps: 33 | - uses: actions/checkout@v3 34 | 35 | - name: Download Package 36 | uses: actions/download-artifact@v3 37 | with: 38 | name: Packages 39 | path: dist 40 | 41 | - name: Publish package to PyPI 42 | uses: pypa/gh-action-pypi-publish@v1.8.5 43 | 44 | - name: Push tag 45 | run: | 46 | git config user.name "pytest bot" 47 | git config user.email "pytestbot@gmail.com" 48 | git tag --annotate --message=${{ github.event.inputs.version }} ${{ github.event.inputs.version }} ${{ github.sha }} 49 | git push origin ${{ github.event.inputs.version }} 50 | 51 | - name: GitHub Release 52 | uses: softprops/action-gh-release@v1 53 | with: 54 | files: dist/* 55 | tag_name: ${{ github.event.inputs.version }} 56 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - "test-me-*" 8 | 9 | pull_request: 10 | 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | 18 | package: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Build and Check Package 23 | uses: hynek/build-and-inspect-python-package@v1.5 24 | 25 | test: 26 | 27 | needs: [package] 28 | 29 | runs-on: ${{ matrix.os }} 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | python: ["3.8", "3.9", "3.10", "3.11", "3.12"] 35 | os: [ubuntu-latest, windows-latest] 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | 40 | - name: Download Package 41 | uses: actions/download-artifact@v3 42 | with: 43 | name: Packages 44 | path: dist 45 | 46 | - name: Set up Python 47 | uses: actions/setup-python@v4 48 | with: 49 | python-version: ${{ matrix.python }} 50 | 51 | - name: Install tox 52 | run: | 53 | python -m pip install --upgrade pip 54 | pip install tox 55 | 56 | - name: Test 57 | shell: bash 58 | run: | 59 | tox run -e py --installpkg `find dist/*.tar.gz` 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python bytecode / optimized files 2 | *.py[co] 3 | *.egg-info 4 | build 5 | sdist 6 | .vscode 7 | *.swp 8 | __pycache__/ 9 | 10 | # virtualenv 11 | venv/ 12 | 13 | # Sphinx documentation 14 | docs/_build 15 | 16 | # Unit test / coverage reports 17 | htmlcov/ 18 | .tox/ 19 | .coverage 20 | .coverage.* 21 | .pytest_cache/ 22 | .eggs/ 23 | dist/ 24 | coverage.xml 25 | tests/junit.xml 26 | 27 | src/pytest_flask/_version.py 28 | 29 | # Editors 30 | .vscode 31 | .code-workspace 32 | .python-version 33 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-byte-order-marker 6 | - id: trailing-whitespace 7 | - id: end-of-file-fixer 8 | - id: fix-encoding-pragma 9 | args: [--remove] 10 | - id: check-yaml 11 | - repo: https://github.com/asottile/reorder-python-imports 12 | rev: v3.14.0 13 | hooks: 14 | - id: reorder-python-imports 15 | args: ['--application-directories=.:src', --py3-plus] 16 | - repo: https://github.com/psf/black 17 | rev: 24.10.0 18 | hooks: 19 | - id: black 20 | - repo: https://github.com/PyCQA/flake8 21 | rev: 7.1.1 22 | hooks: 23 | - id: flake8 24 | additional_dependencies: [flake8-bugbear] 25 | - repo: local 26 | hooks: 27 | - id: rst 28 | name: rst 29 | entry: rst-lint --encoding utf-8 30 | files: ^(RELEASING.rst|README.rst)$ 31 | language: python 32 | additional_dependencies: [pygments, restructuredtext_lint] 33 | - repo: https://github.com/PyCQA/autoflake 34 | rev: v2.3.1 35 | hooks: 36 | - id: autoflake 37 | - repo: https://github.com/pre-commit/mirrors-mypy 38 | rev: v1.14.1 39 | hooks: 40 | - id: mypy 41 | files: ^(src/|tests/) 42 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.12" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | fail_on_warning: true 11 | 12 | formats: 13 | - pdf 14 | - epub 15 | 16 | python: 17 | install: 18 | - method: pip 19 | path: . 20 | - requirements: requirements/docs.txt 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at pytest-dev@python.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | All contributions are greatly appreciated! 5 | 6 | How to report issues 7 | ~~~~~~~~~~~~~~~~~~~~ 8 | 9 | Facilitating the work of potential contributors is recommended since it 10 | increases the likelihood of your issue being solved quickly. The few extra 11 | steps listed below will help clarify problems you might be facing: 12 | 13 | - Include a `minimal reproducible example`_ when possible. 14 | - Describe the expected behavior and what actually happened including a full 15 | trace-back in case of exceptions. 16 | - Make sure to list details about your environment, such as your platform, 17 | versions of pytest, pytest-flask and python release. 18 | 19 | Also, it's important to check the current open issues for similar reports 20 | in order to avoid duplicates. 21 | 22 | .. _minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example 23 | 24 | Setting up your development environment 25 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | 27 | - Fork pytest-flask to your GitHub account by clicking the `Fork`_ button. 28 | - `Clone`_ the main repository (not your fork) to your local machine. 29 | 30 | .. code-block:: text 31 | 32 | $ git clone https://github.com/pytest-dev/pytest-flask 33 | $ cd pytest-flask 34 | 35 | - Add your fork as a remote to push your contributions.Replace 36 | ``{username}`` with your username. 37 | 38 | .. code-block:: text 39 | 40 | $ git remote add fork https://github.com/{username}/pytest-flask 41 | 42 | - Using `Tox`_, create a virtual environment and install pytest-flask in editable mode with development dependencies. 43 | 44 | .. code-block:: text 45 | 46 | $ tox -e dev 47 | $ source venv/bin/activate 48 | 49 | - Install pre-commit hooks 50 | 51 | .. code-block:: text 52 | 53 | $ pre-commit install 54 | 55 | .. _Fork: https://github.com/pytest-dev/pytest-flask/fork 56 | .. _Clone: https://docs.github.com/en/get-started/quickstart/fork-a-repo#step-2-create-a-local-clone-of-your-fork 57 | .. _Tox: https://tox.readthedocs.io/en/latest/ 58 | 59 | Start Coding 60 | ~~~~~~~~~~~~ 61 | 62 | - Create a new branch to identify what feature you are working on. 63 | 64 | .. code-block:: text 65 | 66 | $ git fetch origin 67 | $ git checkout -b your-branch-name origin/master 68 | 69 | - Make your changes 70 | - Include tests that cover any code changes you make and run them 71 | as described below. 72 | - Push your changes to your fork. 73 | `create a pull request`_ describing your changes. 74 | 75 | .. code-block:: text 76 | 77 | $ git push --set-upstream fork your-branch-name 78 | 79 | .. _create a pull request: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork 80 | 81 | How to run tests 82 | ~~~~~~~~~~~~~~~~ 83 | 84 | You can run the test suite for the current environment with 85 | 86 | .. code-block:: text 87 | 88 | $ pytest 89 | 90 | To run the full test suite for all supported python versions 91 | 92 | .. code-block:: text 93 | 94 | $ tox 95 | 96 | Obs. CI will run tox when you submit your pull request, so this is optional. 97 | 98 | Checking Test Coverage 99 | ~~~~~~~~~~~~~~~~~~~~~~~ 100 | 101 | To get a complete report of code sections not being touched by the 102 | test suite run ``pytest`` using ``coverage``. 103 | 104 | .. code-block:: text 105 | 106 | $ coverage run --concurrency=multiprocessing -m pytest 107 | $ coverage combine 108 | $ coverage html 109 | 110 | Open ``htmlcov/index.html`` in your browser. 111 | 112 | More about coverage `here `__. 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2014–2016 Vital Kudzelka and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pytest-flask 2 | ============ 3 | 4 | .. image:: https://img.shields.io/pypi/v/pytest-flask.svg 5 | :target: https://pypi.python.org/pypi/pytest-flask 6 | :alt: PyPi version 7 | 8 | .. image:: https://img.shields.io/conda/vn/conda-forge/pytest-flask.svg 9 | :target: https://anaconda.org/conda-forge/pytest-flask 10 | :alt: conda-forge version 11 | 12 | .. image:: https://github.com/pytest-dev/pytest-flask/workflows/build/badge.svg 13 | :target: https://github.com/pytest-dev/pytest-flask/actions 14 | :alt: CI status 15 | 16 | .. image:: https://img.shields.io/pypi/pyversions/pytest-flask.svg 17 | :target: https://pypi.org/project/pytest-flask 18 | :alt: PyPi downloads 19 | 20 | .. image:: https://readthedocs.org/projects/pytest-flask/badge/?version=latest 21 | :target: https://pytest-flask.readthedocs.org/en/latest/ 22 | :alt: Documentation status 23 | 24 | .. image:: https://img.shields.io/maintenance/yes/2023?color=blue 25 | :target: https://github.com/pytest-dev/pytest-flask 26 | :alt: Maintenance 27 | 28 | .. image:: https://img.shields.io/github/last-commit/pytest-dev/pytest-flask?color=blue 29 | :target: https://github.com/pytest-dev/pytest-flask/commits/master 30 | :alt: GitHub last commit 31 | 32 | .. image:: https://img.shields.io/github/issues-pr-closed-raw/pytest-dev/pytest-flask?color=blue 33 | :target: https://github.com/pytest-dev/pytest-flask/pulls?q=is%3Apr+is%3Aclosed 34 | :alt: GitHub closed pull requests 35 | 36 | .. image:: https://img.shields.io/github/issues-closed/pytest-dev/pytest-flask?color=blue 37 | :target: https://github.com/pytest-dev/pytest-flask/issues?q=is%3Aissue+is%3Aclosed 38 | :alt: GitHub closed issues 39 | 40 | .. image:: https://img.shields.io/pypi/dm/pytest-flask?color=blue 41 | :target: https://pypi.org/project/pytest-flask/ 42 | :alt: PyPI - Downloads 43 | 44 | .. image:: https://img.shields.io/github/languages/code-size/pytest-dev/pytest-flask?color=blue 45 | :target: https://github.com/pytest-dev/pytest-flask 46 | :alt: Code size 47 | 48 | .. image:: https://img.shields.io/badge/license-MIT-blue.svg?color=blue 49 | :target: https://github.com/pytest-dev/pytest-flask/blob/master/LICENSE 50 | :alt: License 51 | 52 | .. image:: https://img.shields.io/github/issues-raw/pytest-dev/pytest-flask.svg?color=blue 53 | :target: https://github.com/pytest-dev/pytest-flask/issues 54 | :alt: Issues 55 | 56 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 57 | :target: https://github.com/ambv/black 58 | :alt: style 59 | 60 | An extension of `pytest`_ test runner which 61 | provides a set of useful tools to simplify testing and development 62 | of the Flask extensions and applications. 63 | 64 | To view a more detailed list of extension features and examples go to 65 | the `PyPI`_ overview page or 66 | `package documentation`_. 67 | 68 | How to start? 69 | ------------- 70 | 71 | Considering the minimal flask `application factory`_ below in ``myapp.py`` as an example: 72 | 73 | .. code-block:: python 74 | 75 | from flask import Flask 76 | 77 | def create_app(): 78 | # create a minimal app 79 | app = Flask(__name__) 80 | 81 | # simple hello world view 82 | @app.route('/hello') 83 | def hello(): 84 | return 'Hello, World!' 85 | 86 | return app 87 | 88 | You first need to define your application fixture in ``conftest.py``: 89 | 90 | .. code-block:: python 91 | 92 | from myapp import create_app 93 | 94 | @pytest.fixture 95 | def app(): 96 | app = create_app() 97 | return app 98 | 99 | Finally, install the extension with dependencies and run your test suite:: 100 | 101 | $ pip install pytest-flask 102 | $ pytest 103 | 104 | Contributing 105 | ------------ 106 | 107 | Don’t hesitate to create a `GitHub issue`_ for any bug or 108 | suggestion. For more information check our contribution `guidelines`_. 109 | 110 | .. _pytest: https://docs.pytest.org/en/stable/ 111 | .. _PyPI: https://pypi.python.org/pypi/pytest-flask 112 | .. _Github issue: https://github.com/vitalk/pytest-flask/issues 113 | .. _package documentation: http://pytest-flask.readthedocs.org/en/latest/ 114 | .. _guidelines: https://github.com/pytest-dev/pytest-flask/blob/master/CONTRIBUTING.rst 115 | .. _application factory: https://flask.palletsprojects.com/patterns/appfactories/ 116 | -------------------------------------------------------------------------------- /RELEASING.rst: -------------------------------------------------------------------------------- 1 | Here are the steps on how to make a new release. 2 | 3 | 1. Create a ``release-VERSION`` branch from ``upstream/main``. 4 | 2. Update ``CHANGELOG.rst``. 5 | 3. Push the branch to ``upstream``. 6 | 4. Once all tests pass, start the ``deploy`` workflow manually. 7 | 5. Merge the PR. 8 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytest-flask.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest-flask.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pytest-flask" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest-flask" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. _changelog: 2 | 3 | Changelog 4 | ========= 5 | 6 | UNRELEASED 7 | ---------- 8 | 9 | * Add support for Python 3.10, 3.11, and 3.12. 10 | * Drop support for EOL Python 3.7. 11 | * Add type hints. 12 | 13 | 1.3.0 (2023-10-23) 14 | ------------------ 15 | 16 | - Fixed compatibility with ``Flask 3.0`` -- the consequence is that the deprecated and incompatible ``request_ctx`` has been removed. 17 | 18 | 1.2.1 19 | ------------------ 20 | - Fix bug in ``:meth:pytest_flask.fixtures.live_server`` 21 | where ``SESSION_COOKIE_DOMAIN`` was set to false due to 22 | ``original_server_name`` defaulting to "localhost". 23 | The new default is "localhost.localdomain". 24 | - Drop support for python 3.6 and 3.5 25 | 26 | 1.2.0 (2021-02-26) 27 | ------------------ 28 | 29 | - Remove deprecated ``:meth:live_server.url`` 30 | - fixture ``request_ctx is now deprecated`` 31 | and will be removed in the future 32 | - ``JSONReponse.json`` removed in favour of 33 | ``Werkzeug.wrappers.Response.json`` 34 | 35 | 1.1.0 (2020-11-08) 36 | ------------------ 37 | 38 | - Speedup live server start time. Use `socket` instead of server 39 | pulling (`#58`_) to check server availability and add new 40 | ``--live-server-wait`` option to set the live server wait timeout. 41 | Thanks to `@jadkik`_. 42 | 43 | 44 | 1.0.0 (2020-03-03) 45 | ------------------ 46 | 47 | **Important** 48 | 49 | - ``live_server`` is now ``session``-scoped by default. This can be changed by using the ``live-server_scope`` option in your ``pytest.ini`` (`#113`_). Thanks `@havok2063`_ for the initial patch and `@TWood67`_ for finishing it up. 50 | 51 | - pytest 5.2 or later is now required. 52 | 53 | - Python 2.7 and 3.4 are no longer supported. 54 | 55 | .. _@havok2063: https://github.com/havok2063 56 | .. _@TWood67: https://github.com/TWood67 57 | .. _#113: https://github.com/pytest-dev/pytest-flask/pull/113 58 | 59 | 0.15.1 (2020-02-03) 60 | ------------------- 61 | 62 | - Fix ``ImportError`` with ``Werkzeug 1.0.0rc1`` (`#105`_). 63 | 64 | .. _#105: https://github.com/pytest-dev/pytest-flask/pull/105 65 | 66 | 0.15.0 (2019-05-13) 67 | ------------------- 68 | 69 | - Properly register the ``options`` marker (`#97`_). 70 | 71 | .. _#97: https://github.com/pytest-dev/pytest-flask/pull/97 72 | 73 | 0.14.0 (2018-10-15) 74 | ------------------- 75 | 76 | - New ``--live-server-host`` command-line option to set the host name used by 77 | the ``live_server`` fixture. 78 | 79 | Thanks `@o1da`_ for the PR (`#90`_). 80 | 81 | .. _@o1da: https://github.com/o1da 82 | .. _#90: https://github.com/pytest-dev/pytest-flask/pull/90 83 | 84 | 0.13.0 (2018-09-29) 85 | ------------------- 86 | 87 | - ``JSONReponse`` now supports comparison directly with status codes: 88 | 89 | .. code-block:: python 90 | 91 | assert client.get('invalid-route', headers=[('Accept', 'application/json')]) == 404 92 | 93 | Thanks `@dusktreader`_ for the PR (`#86`_). 94 | 95 | .. _@dusktreader: https://github.com/dusktreader 96 | .. _#86: https://github.com/pytest-dev/pytest-flask/pull/86 97 | 98 | 0.12.0 (2018-09-06) 99 | ------------------- 100 | 101 | - ``pytest-flask`` now requires ``pytest>=3.6`` (`#84`_). 102 | 103 | - Add new ``--live-server-port`` option to select the port the live server will use (`#82`_). 104 | Thanks `@RazerM`_ for the PR. 105 | 106 | - Now ``live_server`` will try to stop the server cleanly by emitting a ``SIGINT`` signal and 107 | waiting 5 seconds for the server to shutdown. If the server is still running after 5 seconds, 108 | it will be forcefully terminated. This behavior can be changed by passing 109 | ``--no-live-server-clean-stop`` in the command-line (`#49`_). 110 | Thanks `@jadkik`_ for the PR. 111 | 112 | - Internal fixes silence pytest warnings, more visible now with ``pytest-3.8.0`` (`#84`_). 113 | 114 | .. _@jadkik: https://github.com/jadkik 115 | .. _@RazerM: https://github.com/RazerM 116 | .. _#49: https://github.com/pytest-dev/pytest-flask/issues/49 117 | .. _#82: https://github.com/pytest-dev/pytest-flask/pull/82 118 | .. _#84: https://github.com/pytest-dev/pytest-flask/pull/84 119 | 120 | 121 | 0.11.0 (compared to 0.10.0) 122 | --------------------------- 123 | 124 | - Implement deployment using Travis, following in line with many other pytest plugins. 125 | 126 | - Allow live server to handle concurrent requests (`#56`_), thanks to 127 | `@mattwbarry`_ for the PR. 128 | 129 | - Fix broken link to pytest documentation (`#50`_), thanks to 130 | `@jineshpaloor`_ for the PR. 131 | 132 | - Tox support (`#48`_), thanks to `@steenzout`_ for the PR. 133 | 134 | - Add ``LICENSE`` into distribution (`#43`_), thanks to `@danstender`_. 135 | 136 | - Minor typography improvements in documentation. 137 | 138 | - Add changelog to documentation. 139 | 140 | 141 | .. _#43: https://github.com/vitalk/pytest-flask/issues/43 142 | .. _#48: https://github.com/pytest-dev/pytest-flask/pull/48 143 | .. _#50: https://github.com/pytest-dev/pytest-flask/pull/50 144 | .. _#56: https://github.com/pytest-dev/pytest-flask/pull/56 145 | .. _#58: steenzouthttps://github.com/pytest-dev/pytest-flask/pull/58 146 | .. _@danstender: https://github.com/danstender 147 | .. _@jadkik: https://github.com/jadkik 148 | .. _@jineshpaloor: https://github.com/jineshpaloor 149 | .. _@mattwbarry: https://github.com/mattwbarry 150 | .. _@steenzout: https://github.com/steenzout 151 | 152 | 153 | 0.10.0 (compared to 0.9.0) 154 | -------------------------- 155 | 156 | - Add ``--start-live-server``/``--no-start-live-server`` options to prevent 157 | live server from starting automatically (`#36`_), thanks to `@EliRibble`_. 158 | 159 | - Fix title formatting in documentation. 160 | 161 | 162 | .. _#36: https://github.com/vitalk/pytest-flask/issues/36 163 | .. _@EliRibble: https://github.com/EliRibble 164 | 165 | 166 | 0.9.0 (compared to 0.8.1) 167 | ------------------------- 168 | 169 | - Rename marker used to pass options to application, e.g. ``pytest.mark.app`` 170 | is now ``pytest.mark.options`` (`#35`_). 171 | 172 | - Documentation badge points to the package documentation. 173 | 174 | - Add Travis CI configuration to ensure the tests are passed in supported 175 | environments (`#32`_). 176 | 177 | 178 | .. _#32: https://github.com/vitalk/pytest-flask/issues/32 179 | .. _#35: https://github.com/vitalk/pytest-flask/issues/35 180 | 181 | 0.8.1 182 | ----- 183 | 184 | - Minor changes in documentation. 185 | 186 | 0.8.0 187 | ----- 188 | 189 | - New ``request_ctx`` fixture which contains all request relevant 190 | information (`#29`_). 191 | 192 | .. _#29: https://github.com/vitalk/pytest-flask/issues/29 193 | 194 | 0.7.5 195 | ----- 196 | 197 | - Use pytest ``monkeypath`` fixture to teardown application config (`#27`_). 198 | 199 | .. _#27: https://github.com/vitalk/pytest-flask/issues/27 200 | 201 | 0.7.4 202 | ----- 203 | 204 | - Better test coverage, e.g. tests for available fixtures and markers. 205 | 206 | 0.7.3 207 | ----- 208 | 209 | - Use retina-ready badges in documentation (`#21`_). 210 | 211 | .. _#21: https://github.com/vitalk/pytest-flask/issues/21 212 | 213 | 0.7.2 214 | ----- 215 | 216 | - Use pytest ``monkeypatch`` fixture to rewrite live server name. 217 | 218 | 0.7.1 219 | ----- 220 | 221 | - Single-sourcing package version (`#24`_), as per `"Python Packaging User Guide" 222 | `_. 223 | 224 | .. _#24: https://github.com/vitalk/pytest-flask/issues/24 225 | 226 | 0.7.0 227 | ----- 228 | 229 | - Add package documentation (`#20`_). 230 | 231 | .. _#20: https://github.com/vitalk/pytest-flask/issues/20 232 | 233 | 0.6.3 234 | ----- 235 | 236 | - Better documentation in README with reST formatting (`#18`_), thanks 237 | to `@greedo`_. 238 | 239 | 240 | .. _#18: https://github.com/vitalk/pytest-flask/issues/18 241 | .. _@greedo: https://github.com/greedo 242 | 243 | 0.6.2 244 | ----- 245 | 246 | - Release the random port before starting the application live server (`#17`_), 247 | thanks to `@davehunt`_. 248 | 249 | 250 | .. _#17: https://github.com/vitalk/pytest-flask/issues/17 251 | .. _@davehunt: https://github.com/davehunt 252 | 253 | 0.6.1 254 | ----- 255 | 256 | - Bind live server to a random port instead of 5000 or whatever is passed on 257 | the command line, so it’s possible to execute tests in parallel via 258 | pytest-dev/pytest-xdist (`#15`_). Thanks to `@davehunt`_. 259 | 260 | - Remove ``--liveserver-port`` option. 261 | 262 | 263 | .. _#15: https://github.com/vitalk/pytest-flask/issues/15 264 | .. _@davehunt: https://github.com/davehunt 265 | 266 | 0.6.0 267 | ----- 268 | 269 | - Fix typo in option help for ``--liveserver-port``, thanks to `@svenstaro`_. 270 | 271 | .. _@svenstaro: https://github.com/svenstaro 272 | 273 | 0.5.0 274 | ----- 275 | 276 | - Add ``live_server`` fixture uses to run application in the background (`#11`_), 277 | thanks to `@svenstaro`_. 278 | 279 | 280 | .. _#11: https://github.com/vitalk/pytest-flask/issues/11 281 | .. _@svenstaro: https://github.com/svenstaro 282 | 283 | 0.4.0 284 | ----- 285 | 286 | - Add ``client_class`` fixture for class-based tests. 287 | 288 | 0.3.4 289 | ----- 290 | 291 | - Include package requirements into distribution (`#8`_). 292 | 293 | .. _#8: https://github.com/vitalk/pytest-flask/issues/8 294 | 295 | 0.3.3 296 | ----- 297 | 298 | - Explicitly pin package dependencies and their versions. 299 | 300 | 0.3.2 301 | ----- 302 | 303 | - Use ``codecs`` module to open files to prevent possible errors on open 304 | files which contains non-ascii characters. 305 | 306 | 0.3.1 307 | ----- 308 | 309 | First release on PyPI. 310 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # pytest-flask documentation build configuration file, created by 3 | # sphinx-quickstart on Thu Feb 19 17:57:27 2015. 4 | # 5 | # This file is execfile()d with the current directory set to its 6 | # containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | import datetime 14 | import os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ------------------------------------------------ 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | # needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be 27 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 28 | # ones. 29 | extensions = [] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ["_templates"] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = ".rst" 36 | 37 | # The encoding of source files. 38 | # source_encoding = 'utf-8-sig' 39 | 40 | # The master toctree document. 41 | master_doc = "index" 42 | 43 | # General information about the project. 44 | project = "pytest-flask" 45 | copyright = "%d, Vital Kudzelka and contributors" % datetime.date.today().year 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | # The short X.Y version. 52 | version = __import__("pytest_flask").__version__ 53 | # The full version, including alpha/beta/rc tags. 54 | release = __import__("pytest_flask").__version__ 55 | 56 | # The language for content autogenerated by Sphinx. Refer to documentation 57 | # for a list of supported languages. 58 | # language = None 59 | 60 | # There are two options for replacing |today|: either, you set today to some 61 | # non-false value, then it is used: 62 | # today = '' 63 | # Else, today_fmt is used as the format for a strftime call. 64 | # today_fmt = '%B %d, %Y' 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | exclude_patterns = ["_build"] 69 | 70 | # The reST default role (used for this markup: `text`) to use for all 71 | # documents. 72 | # default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | # add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | # add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | # show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = "sphinx" 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | # modindex_common_prefix = [] 90 | 91 | # If true, keep warnings as "system message" paragraphs in the built documents. 92 | # keep_warnings = False 93 | 94 | 95 | # -- Options for HTML output ---------------------------------------------- 96 | 97 | # Only import and set the theme if we're building docs locally 98 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 99 | 100 | if not on_rtd: 101 | import sphinx_rtd_theme 102 | 103 | html_theme = "sphinx_rtd_theme" 104 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 105 | else: 106 | html_theme = "default" 107 | 108 | # Theme options are theme-specific and customize the look and feel of a theme 109 | # further. For a list of options available for each theme, see the 110 | # documentation. 111 | # html_theme_options = {} 112 | 113 | # Add any paths that contain custom themes here, relative to this directory. 114 | # html_theme_path = [] 115 | 116 | # The name for this set of Sphinx documents. If None, it defaults to 117 | # " v documentation". 118 | # html_title = None 119 | 120 | # A shorter title for the navigation bar. Default is the same as html_title. 121 | # html_short_title = None 122 | 123 | # The name of an image file (relative to this directory) to place at the top 124 | # of the sidebar. 125 | # html_logo = None 126 | 127 | # The name of an image file (within the static path) to use as favicon of the 128 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 129 | # pixels large. 130 | # html_favicon = None 131 | 132 | # Add any paths that contain custom static files (such as style sheets) here, 133 | # relative to this directory. They are copied after the builtin static files, 134 | # so a file named "default.css" will overwrite the builtin "default.css". 135 | # html_static_path = ['_static'] 136 | 137 | # Add any extra paths that contain custom files (such as robots.txt or 138 | # .htaccess) here, relative to this directory. These files are copied 139 | # directly to the root of the documentation. 140 | # html_extra_path = [] 141 | 142 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 143 | # using the given strftime format. 144 | # html_last_updated_fmt = '%b %d, %Y' 145 | 146 | # If true, SmartyPants will be used to convert quotes and dashes to 147 | # typographically correct entities. 148 | # html_use_smartypants = True 149 | 150 | # Custom sidebar templates, maps document names to template names. 151 | # html_sidebars = {} 152 | 153 | # Additional templates that should be rendered to pages, maps page names to 154 | # template names. 155 | # html_additional_pages = {} 156 | 157 | # If false, no module index is generated. 158 | # html_domain_indices = True 159 | 160 | # If false, no index is generated. 161 | # html_use_index = True 162 | 163 | # If true, the index is split into individual pages for each letter. 164 | # html_split_index = False 165 | 166 | # If true, links to the reST sources are added to the pages. 167 | # html_show_sourcelink = True 168 | 169 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 170 | # html_show_sphinx = True 171 | 172 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 173 | # html_show_copyright = True 174 | 175 | # If true, an OpenSearch description file will be output, and all pages will 176 | # contain a tag referring to it. The value of this option must be the 177 | # base URL from which the finished HTML is served. 178 | # html_use_opensearch = '' 179 | 180 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 181 | # html_file_suffix = None 182 | 183 | # Output file base name for HTML help builder. 184 | htmlhelp_basename = "pytest-flaskdoc" 185 | 186 | 187 | # -- Options for LaTeX output --------------------------------------------- 188 | 189 | latex_elements = { 190 | # The paper size ('letterpaper' or 'a4paper'). 191 | # 'papersize': 'letterpaper', 192 | # The font size ('10pt', '11pt' or '12pt'). 193 | # 'pointsize': '10pt', 194 | # Additional stuff for the LaTeX preamble. 195 | # 'preamble': '', 196 | } 197 | 198 | # Grouping the document tree into LaTeX files. List of tuples 199 | # (source start file, target name, title, 200 | # author, documentclass [howto, manual, or own class]). 201 | latex_documents = [ 202 | ( 203 | "index", 204 | "pytest-flask.tex", 205 | "pytest-flask Documentation", 206 | "Vital Kudzelka", 207 | "manual", 208 | ), 209 | ] 210 | 211 | # The name of an image file (relative to this directory) to place at the top of 212 | # the title page. 213 | # latex_logo = None 214 | 215 | # For "manual" documents, if this is true, then toplevel headings are parts, 216 | # not chapters. 217 | # latex_use_parts = False 218 | 219 | # If true, show page references after internal links. 220 | # latex_show_pagerefs = False 221 | 222 | # If true, show URL addresses after external links. 223 | # latex_show_urls = False 224 | 225 | # Documents to append as an appendix to all manuals. 226 | # latex_appendices = [] 227 | 228 | # If false, no module index is generated. 229 | # latex_domain_indices = True 230 | 231 | 232 | # -- Options for manual page output --------------------------------------- 233 | 234 | # One entry per manual page. List of tuples 235 | # (source start file, name, description, authors, manual section). 236 | man_pages = [ 237 | ("index", "pytest-flask", "pytest-flask Documentation", ["Vital Kudzelka"], 1) 238 | ] 239 | 240 | # If true, show URL addresses after external links. 241 | # man_show_urls = False 242 | 243 | 244 | # -- Options for Texinfo output ------------------------------------------- 245 | 246 | # Grouping the document tree into Texinfo files. List of tuples 247 | # (source start file, target name, title, author, 248 | # dir menu entry, description, category) 249 | texinfo_documents = [ 250 | ( 251 | "index", 252 | "pytest-flask", 253 | "pytest-flask Documentation", 254 | "Vital Kudzelka", 255 | "pytest-flask", 256 | "One line description of project.", 257 | "Miscellaneous", 258 | ), 259 | ] 260 | 261 | # Documents to append as an appendix to all manuals. 262 | # texinfo_appendices = [] 263 | 264 | # If false, no module index is generated. 265 | # texinfo_domain_indices = True 266 | 267 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 268 | # texinfo_show_urls = 'footnote' 269 | 270 | # If true, do not generate a @detailmenu in the "Top" node's menu. 271 | # texinfo_no_detailmenu = False 272 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | 3 | .. include:: ../CONTRIBUTING.rst 4 | -------------------------------------------------------------------------------- /docs/features.rst: -------------------------------------------------------------------------------- 1 | .. _features: 2 | 3 | Feature reference 4 | ================= 5 | 6 | Extension provides some sugar for your tests, such as: 7 | 8 | * Access to context bound objects (``url_for``, ``request``, ``session``) 9 | without context managers: 10 | 11 | .. code:: python 12 | 13 | def test_app(client): 14 | assert client.get(url_for('myview')).status_code == 200 15 | 16 | * Easy access to ``JSON`` data in response: 17 | 18 | .. code:: python 19 | 20 | @api.route('/ping') 21 | def ping(): 22 | return jsonify(ping='pong') 23 | 24 | def test_api_ping(client): 25 | res = client.get(url_for('api.ping')) 26 | assert res.json == {'ping': 'pong'} 27 | 28 | .. note:: 29 | 30 | User-defined ``json`` attribute/method in application response class will 31 | not be overwritten. So you can define your own response deserialization method: 32 | 33 | .. code:: python 34 | 35 | from flask import Response 36 | from myapp import create_app 37 | 38 | class MyResponse(Response): 39 | '''Implements custom deserialization method for response objects.''' 40 | @property 41 | def json(self): 42 | return 42 43 | 44 | @pytest.fixture(scope="session") 45 | def app(): 46 | app = create_app() 47 | app.response_class = MyResponse 48 | return app 49 | 50 | def test_my_json_response(client): 51 | res = client.get(url_for('api.ping')) 52 | assert res.json == 42 53 | 54 | * Running tests in parallel with `pytest-xdist`_. This can lead to 55 | significant speed improvements on multi core/multi CPU machines. 56 | 57 | This requires the ``pytest-xdist`` plugin to be available, it can usually be 58 | installed with:: 59 | 60 | pip install pytest-xdist 61 | 62 | You can then run the tests by running:: 63 | 64 | pytest -n 65 | 66 | **Not enough pros?** See the full list of available fixtures and markers 67 | below. 68 | 69 | 70 | Fixtures 71 | -------- 72 | 73 | ``pytest-flask`` provides a list of useful fixtures to simplify application 74 | testing. More information on fixtures and their usage is available in the 75 | `pytest documentation`_. 76 | 77 | 78 | ``client`` - application test client 79 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | An instance of ``app.test_client``. Typically refers to 82 | `flask.Flask.test_client`_. 83 | 84 | .. hint:: 85 | 86 | During test execution a request context will be automatically pushed 87 | for you, so context-bound methods can be conveniently called (e.g. 88 | ``url_for``, ``session``. 89 | 90 | Example: 91 | 92 | .. code:: python 93 | 94 | def test_myview(client): 95 | assert client.get(url_for('myview')).status_code == 200 96 | 97 | 98 | ``client_class`` - application test client for class-based tests 99 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100 | 101 | Example: 102 | 103 | .. code:: python 104 | 105 | @pytest.mark.usefixtures('client_class') 106 | class TestSuite: 107 | 108 | def test_myview(self): 109 | assert self.client.get(url_for('myview')).status_code == 200 110 | 111 | 112 | ``config`` - application config 113 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 114 | 115 | An instance of ``app.config``. Typically refers to `flask.Config`_. 116 | 117 | 118 | ``live_server`` - application live server 119 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 | 121 | Run application in a separate process (useful for tests with Selenium_ and 122 | other headless browsers). 123 | 124 | .. hint:: 125 | 126 | The server’s URL can be retrieved using the ``url_for`` function. 127 | 128 | .. code:: python 129 | 130 | from flask import url_for 131 | 132 | @pytest.mark.usefixtures('live_server') 133 | class TestLiveServer: 134 | 135 | def test_server_is_up_and_running(self): 136 | res = urllib2.urlopen(url_for('index', _external=True)) 137 | assert b'OK' in res.read() 138 | assert res.code == 200 139 | 140 | 141 | ``--start-live-server`` - start live server automatically (default) 142 | ``````````````````````````````````````````````````````````````````` 143 | 144 | 145 | ``--no-start-live-server`` - don’t start live server automatically 146 | `````````````````````````````````````````````````````````````````` 147 | 148 | By default the server will start automatically whenever you reference 149 | ``live_server`` fixture in your tests. But starting live server imposes some 150 | high costs on tests that need it when they may not be ready yet. To prevent 151 | that behavior pass ``--no-start-live-server`` into your default options (for 152 | example, in your project’s ``pytest.ini`` file):: 153 | 154 | [pytest] 155 | addopts = --no-start-live-server 156 | 157 | .. note:: 158 | 159 | You **should manually start** live server after you finish your application 160 | configuration and define all required routes: 161 | 162 | .. code:: python 163 | 164 | def test_add_endpoint_to_live_server(live_server): 165 | @live_server.app.route('/test-endpoint') 166 | def test_endpoint(): 167 | return 'got it', 200 168 | 169 | live_server.start() 170 | 171 | res = urlopen(url_for('test_endpoint', _external=True)) 172 | assert res.code == 200 173 | assert b'got it' in res.read() 174 | 175 | 176 | ``--live-server-wait`` - the live server wait timeout (5 seconds) 177 | ````````````````````````````````````````````````````````````````` 178 | The timeout after which test case is aborted if live server is not started. 179 | 180 | 181 | ``--live-server-port`` - use a fixed port 182 | ````````````````````````````````````````` 183 | By default the server uses a random port. In some cases it is desirable to run 184 | the server with a fixed port. You can use ``--live-server-port`` (for example, 185 | in your project's ``pytest.ini`` file):: 186 | 187 | [pytest] 188 | addopts = --live-server-port=5000 189 | 190 | 191 | ``live_server_scope`` - set the scope of the live server 192 | `````````````````````````````````````````````````````````````````` 193 | 194 | By default, the server will be scoped to ``session`` for performance reasons, however 195 | if your server has global state and you want better test isolation, you can use the 196 | ``live_server_scope`` ini option to change the fixture scope: 197 | 198 | .. code-block:: ini 199 | 200 | [pytest] 201 | live_server_scope = function 202 | 203 | 204 | HTTP Request 205 | ~~~~~~~~~~~~~~~~~~~ 206 | 207 | Common request methods are available through the internals of the `Flask API`_. 208 | Specifically, the API creates the default `flask.Flask.test_client`_ instance, 209 | which works like a regular `Werkzeug test client`_. 210 | 211 | Examples: 212 | 213 | .. code:: python 214 | 215 | def test_post_request(client, live_server): 216 | @live_server.app.route('/load-data') 217 | def get_endpoint(): 218 | return url_for('name.load', _external=True) 219 | 220 | live_server.start() 221 | 222 | res = client.post( 223 | get_endpoint(), 224 | headers={'Content-Type': 'application/json'}, 225 | data={} 226 | ) 227 | 228 | assert res.status_code == 200 229 | 230 | .. code:: python 231 | 232 | def test_get_request(client, live_server): 233 | @live_server.app.route('/load-data') 234 | def get_endpoint(): 235 | return url_for('name.load', _external=True) 236 | 237 | live_server.start() 238 | 239 | res = client.get(get_endpoint()) 240 | 241 | assert res.status_code == 200 242 | 243 | .. note:: 244 | 245 | The notation ``name.load_data``, corresponds to a ``endpoint='load'`` 246 | attribute, within a route decorator. The following is a route decorator 247 | using the `blueprint`_ implementation: 248 | 249 | .. code:: python 250 | 251 | from flask import Blueprint, request 252 | 253 | # local variables 254 | blueprint = Blueprint( 255 | 'name', 256 | __name__, 257 | template_folder='interface/templates', 258 | static_folder='interface/static' 259 | ) 260 | 261 | @blueprint.route('/load-data', methods=['POST'], endpoint='load') 262 | def load_data(): 263 | if request.method == 'POST': 264 | if request.get_json(): 265 | pass 266 | 267 | Alternatively, the route function can be referenced directly from the 268 | ``live_server`` implementation, rather than implementing an ``endpoint``: 269 | 270 | .. code:: python 271 | 272 | def test_load_data(live_server, client): 273 | @live_server.app.route('/load-data', methods=['POST']) 274 | def load_data(): 275 | pass 276 | 277 | live_server.start() 278 | 279 | res = client.post(url_for('load_data'), data={}) 280 | assert res.status_code == 200 281 | 282 | .. note:: 283 | 284 | Remember to explicitly define which ``methods`` are supported when 285 | registering the above route function. 286 | 287 | 288 | Content negotiation 289 | ~~~~~~~~~~~~~~~~~~~ 290 | 291 | An important part of any :abbr:`REST (REpresentational State Transfer)` 292 | service is content negotiation. It allows you to implement behaviour such as 293 | selecting a different serialization scheme for different media types. 294 | 295 | HTTP has provisions for several mechanisms for "content negotiation" - the 296 | process of selecting the best representation for a given response 297 | when there are multiple representations available. 298 | 299 | -- :rfc:`2616#section-12`. Fielding, et al. 300 | 301 | The most common way to select one of the multiple possible representations is 302 | via ``Accept`` request header. The following series of ``accept_*`` fixtures 303 | provides an easy way to test content negotiation in your application: 304 | 305 | .. code:: python 306 | 307 | def test_api_endpoint(accept_json, client): 308 | res = client.get(url_for('api.endpoint'), headers=accept_json) 309 | assert res.mimetype == 'application/json' 310 | 311 | 312 | ``accept_any`` - :mimetype:`*/*` accept header 313 | `````````````````````````````````````````````` 314 | 315 | :mimetype:`*/*` accept header suitable to use as parameter in ``client``. 316 | 317 | 318 | ``accept_json`` - :mimetype:`application/json` accept header 319 | ```````````````````````````````````````````````````````````` 320 | 321 | :mimetype:`application/json` accept header suitable to use as parameter in 322 | ``client``. 323 | 324 | 325 | ``accept_jsonp`` - :mimetype:`application/json-p` accept header 326 | ``````````````````````````````````````````````````````````````` 327 | 328 | :mimetype:`application/json-p` accept header suitable to use as parameter in 329 | ``client``. 330 | 331 | 332 | Markers 333 | ------- 334 | 335 | ``pytest-flask`` registers the following markers. See the pytest documentation 336 | on `what markers are`_ and for notes on `using them`_. 337 | 338 | 339 | ``pytest.mark.options`` - pass options to your application config 340 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 341 | 342 | .. py:function:: pytest.mark.options(**kwargs) 343 | 344 | The mark used to pass options to your application config. 345 | 346 | :type kwargs: dict 347 | :param kwargs: 348 | The dictionary used to extend application config. 349 | 350 | Example usage: 351 | 352 | .. code:: python 353 | 354 | @pytest.mark.options(debug=False) 355 | def test_app(app): 356 | assert not app.debug, 'Ensure the app is not in debug mode' 357 | 358 | 359 | .. _pytest-xdist: https://pypi.org/project/pytest-xdist/ 360 | .. _pytest documentation: https://pytest.org/en/latest/fixture.html 361 | .. _flask.Flask.test_client: https://flask.palletsprojects.com/api/#flask.Flask.test_client 362 | .. _flask.Config: https://flask.palletsprojects.com/api/#flask.Config 363 | .. _Selenium: https://selenium-python.readthedocs.io/ 364 | .. _what markers are: https://pytest.org/en/latest/mark.html 365 | .. _using them: https://pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules 366 | .. _Flask API: https://flask.palletsprojects.com/api/ 367 | .. _Werkzeug test client: https://werkzeug.palletsprojects.com/test/#werkzeug.test.Client 368 | .. _blueprint: https://flask.palletsprojects.com/blueprints/ 369 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to pytest-flask’s documentation! 2 | ======================================== 3 | 4 | Pytest-flask is a plugin for `pytest `_ that provides 5 | a set of useful tools to test `Flask `_ applications 6 | and extensions. 7 | 8 | 9 | Quickstart 10 | ---------- 11 | 12 | Install plugin via ``pip``:: 13 | 14 | pip install pytest-flask 15 | 16 | Define your application fixture in ``conftest.py``: 17 | 18 | .. code:: python 19 | 20 | from myapp import create_app 21 | 22 | @pytest.fixture 23 | def app(): 24 | app = create_app() 25 | return app 26 | 27 | Now you can use the ``app`` fixture in your test suite. You can run your tests 28 | with:: 29 | 30 | pytest 31 | 32 | 33 | User’s Guide 34 | ------------ 35 | 36 | This part of the documentation will show you how to get started in using 37 | pytest-flask with your application. 38 | 39 | .. toctree:: 40 | :maxdepth: 4 41 | 42 | tutorial 43 | features 44 | contributing 45 | changelog 46 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pytest-flask.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pytest-flask.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | Pytest is capable to pick up and run existing tests without any or little 5 | configuration. This section describes how to get started quickly. 6 | 7 | Step 1. Install 8 | --------------- 9 | 10 | ``pytest-flask`` is available on `PyPi`_, and can be easily installed via 11 | ``pip``:: 12 | 13 | pip install pytest-flask 14 | 15 | 16 | Step 2. Configure 17 | ----------------- 18 | 19 | Define your application fixture in ``conftest.py``: 20 | 21 | .. code:: python 22 | 23 | from myapp import create_app 24 | 25 | @pytest.fixture 26 | def app(): 27 | app = create_app() 28 | return app 29 | 30 | 31 | Step 3. Run your test suite 32 | --------------------------- 33 | 34 | Use the ``pytest`` command to run your test suite:: 35 | 36 | pytest 37 | 38 | .. note:: Test discovery. 39 | 40 | Pytest `discovers your tests`_ and has a built-in integration with other 41 | testing tools (such as ``nose``, ``unittest`` and ``doctest``). More 42 | comprehensive examples and use cases can be found in the `official 43 | documentation`_. 44 | 45 | 46 | What’s next? 47 | ------------ 48 | 49 | The :ref:`features` section gives a more detailed view of available features, as 50 | well as test fixtures and markers. 51 | 52 | Consult the `pytest documentation `_ for more 53 | information about pytest itself. 54 | 55 | If you want to contribute to the project, see the :ref:`contributing` section. 56 | 57 | 58 | .. _PyPi: https://pypi.org/project/pytest-flask/ 59 | .. _discovers your tests: http://docs.pytest.org/en/latest/goodpractices.html#test-discovery 60 | .. _official documentation: http://pytest.org/latest/usage.html 61 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "setuptools-scm[toml]", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [tool.mypy] 9 | warn_unreachable = true 10 | warn_unused_ignores = true 11 | warn_redundant_casts = true 12 | enable_error_code = [ 13 | "ignore-without-code", 14 | "truthy-bool", 15 | ] 16 | -------------------------------------------------------------------------------- /requirements/docs.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | sphinx_rtd_theme 3 | -------------------------------------------------------------------------------- /requirements/main.txt: -------------------------------------------------------------------------------- 1 | pytest>=5.2 2 | Flask 3 | Werkzeug 4 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | mock 2 | pylint 3 | coverage 4 | pytest-pep8 5 | mypy 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license = MIT 3 | name = pytest-flask 4 | license_files = LICENSE 5 | author = Vital Kudzelka 6 | keywords = pytest, flask, testing 7 | author_email = vital.kudzelka@gmail.com 8 | url = https://github.com/pytest-dev/pytest-flask 9 | long_description = file: README.rst, docs/changelog.rst, LICENSE 10 | long_description_content_type = text/x-rst 11 | description = A set of py.test fixtures to test Flask applications. 12 | project_urls = 13 | Source = https://github.com/pytest-dev/pytest-flask 14 | Issue tracker = https://github.com/pytest-dev/pytest-flask/issues 15 | classifiers= 16 | Framework :: Pytest 17 | Environment :: Plugins 18 | Programming Language :: Python 19 | Environment :: Web Environment 20 | Intended Audience :: Developers 21 | Operating System :: OS Independent 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Programming Language :: Python :: 3.11 26 | Programming Language :: Python :: 3.12 27 | License :: OSI Approved :: MIT License 28 | Topic :: Software Development :: Testing 29 | Development Status :: 5 - Production/Stable 30 | Topic :: Software Development :: Libraries :: Python Modules 31 | 32 | [options] 33 | packages = pytest_flask 34 | zip_safe = False 35 | python_requires = >= 3.8 36 | setup_requires = setuptools_scm 37 | package_dir = 38 | =src 39 | 40 | [options.packages.find] 41 | exclude = docs, tests 42 | 43 | [options.entry_points] 44 | pytest11 = 45 | flask = pytest_flask.plugin 46 | 47 | [flake8] 48 | # B = bugbear 49 | # E = pycodestyle errors 50 | # F = flake8 pyflakes 51 | # W = pycodestyle warnings 52 | # B9 = bugbear opinions 53 | # E203 = slice notation whitespace, invalid 54 | # E501 = line length, handled by bugbear B950 55 | # E722 = bare except, handled by bugbear B001 56 | # W503 = bin op line break, invalid 57 | # F401 = unused imports, false positives 58 | # F811 = redefinition of variables, flase positives 59 | select = B, E, F, W, B9 60 | ignore = 61 | E203 62 | E501 63 | E722 64 | W503 65 | F401 66 | F811 67 | E704 68 | max-line-length = 80 69 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pathlib import Path 3 | 4 | from setuptools import setup 5 | 6 | tests_require = [] 7 | requirements = Path("requirements/main.txt").read_text(encoding="UTF-8").splitlines() 8 | extras_require = { 9 | "docs": Path("requirements/docs.txt").read_text(encoding="UTF-8").splitlines(), 10 | "tests": [tests_require], 11 | } 12 | 13 | setup( 14 | # Dependencies are here for GitHub's dependency graph. 15 | use_scm_version={"write_to": "src/pytest_flask/_version.py"}, 16 | install_requires=requirements, 17 | tests_require=tests_require, 18 | extras_require=extras_require, 19 | ) 20 | -------------------------------------------------------------------------------- /src/pytest_flask/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from ._version import version as __version__ 3 | -------------------------------------------------------------------------------- /src/pytest_flask/_internal.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import warnings 3 | from typing import Callable 4 | from typing import Literal 5 | 6 | from pytest import Config as _PytestConfig 7 | 8 | 9 | _PytestScopeName = Literal["session", "package", "module", "class", "function"] 10 | 11 | 12 | def deprecated(reason: str) -> Callable: 13 | """Decorator which can be used to mark function or method as deprecated. 14 | It will result a warning being emitted when the function is called.""" 15 | 16 | def decorator(func): 17 | @functools.wraps(func) 18 | def deprecated_call(*args, **kwargs): 19 | warnings.simplefilter("always", DeprecationWarning) 20 | warnings.warn(reason, DeprecationWarning, stacklevel=2) 21 | warnings.simplefilter("default", DeprecationWarning) 22 | return func(*args, **kwargs) 23 | 24 | return deprecated_call 25 | 26 | return decorator 27 | 28 | 29 | def _rewrite_server_name(server_name: str, new_port: str) -> str: 30 | """Rewrite server port in ``server_name`` with ``new_port`` value.""" 31 | sep = ":" 32 | if sep in server_name: 33 | server_name, _ = server_name.split(sep, 1) 34 | return sep.join((server_name, new_port)) 35 | 36 | 37 | def _determine_scope(*, fixture_name: str, config: _PytestConfig) -> _PytestScopeName: 38 | return config.getini("live_server_scope") 39 | 40 | 41 | def _make_accept_header(mimetype): 42 | return [("Accept", mimetype)] 43 | -------------------------------------------------------------------------------- /src/pytest_flask/fixtures.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import socket 3 | from typing import Any 4 | from typing import cast 5 | from typing import Generator 6 | 7 | import pytest 8 | from flask import Flask as _FlaskApp 9 | from flask.config import Config as _FlaskAppConfig 10 | from flask.testing import FlaskClient as _FlaskTestClient 11 | from pytest import Config as _PytestConfig 12 | from pytest import FixtureRequest as _PytestFixtureRequest 13 | 14 | from ._internal import _determine_scope 15 | from ._internal import _make_accept_header 16 | from ._internal import _rewrite_server_name 17 | from .live_server import LiveServer 18 | 19 | 20 | @pytest.fixture 21 | def client(app: _FlaskApp) -> Generator[_FlaskTestClient, Any, Any]: 22 | """A Flask test client. An instance of :class:`flask.testing.TestClient` 23 | by default. 24 | """ 25 | with app.test_client() as client: 26 | yield client 27 | 28 | 29 | @pytest.fixture 30 | def client_class(request: _PytestFixtureRequest, client: _FlaskTestClient) -> None: 31 | """Uses to set a ``client`` class attribute to current Flask test client:: 32 | 33 | @pytest.mark.usefixtures('client_class') 34 | class TestView: 35 | 36 | def login(self, email, password): 37 | credentials = {'email': email, 'password': password} 38 | return self.client.post(url_for('login'), data=credentials) 39 | 40 | def test_login(self): 41 | assert self.login('foo@example.com', 'pass').status_code == 200 42 | 43 | """ 44 | if request.cls is not None: 45 | request.cls.client = client 46 | 47 | 48 | @pytest.fixture(scope=_determine_scope) 49 | def live_server( 50 | request: _PytestFixtureRequest, app: _FlaskApp, pytestconfig: _PytestConfig 51 | ) -> Generator[LiveServer, Any, Any]: # pragma: no cover 52 | """Run application in a separate process. 53 | 54 | When the ``live_server`` fixture is applied, the ``url_for`` function 55 | works as expected:: 56 | 57 | def test_server_is_up_and_running(live_server): 58 | index_url = url_for('index', _external=True) 59 | assert index_url == 'http://localhost:5000/' 60 | 61 | res = urllib2.urlopen(index_url) 62 | assert res.code == 200 63 | 64 | """ 65 | # Set or get a port 66 | port = app.config.get("LIVESERVER_PORT", None) 67 | if not port: 68 | port = pytestconfig.getvalue("live_server_port") 69 | 70 | if port == 0: 71 | # Bind to an open port 72 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 73 | s.bind(("", 0)) 74 | port = s.getsockname()[1] 75 | s.close() 76 | 77 | host = cast(str, pytestconfig.getvalue("live_server_host")) 78 | 79 | # Explicitly set application ``SERVER_NAME`` for test suite 80 | original_server_name = app.config["SERVER_NAME"] or "localhost.localdomain" 81 | final_server_name = _rewrite_server_name(original_server_name, str(port)) 82 | app.config["SERVER_NAME"] = final_server_name 83 | 84 | wait = cast(int, request.config.getvalue("live_server_wait")) 85 | clean_stop = cast(bool, request.config.getvalue("live_server_clean_stop")) 86 | 87 | server = LiveServer(app, host, port, wait, clean_stop) 88 | if request.config.getvalue("start_live_server"): 89 | server.start() 90 | 91 | request.addfinalizer(server.stop) 92 | 93 | yield server 94 | 95 | if original_server_name is not None: 96 | app.config["SERVER_NAME"] = original_server_name 97 | 98 | 99 | @pytest.fixture 100 | def config(app: _FlaskApp) -> _FlaskAppConfig: 101 | """An application config.""" 102 | return app.config 103 | 104 | 105 | @pytest.fixture(params=["application/json", "text/html"]) 106 | def mimetype(request) -> str: 107 | return request.param 108 | 109 | 110 | @pytest.fixture 111 | def accept_mimetype(mimetype): 112 | return _make_accept_header(mimetype) 113 | 114 | 115 | @pytest.fixture 116 | def accept_json(request): 117 | return _make_accept_header("application/json") 118 | 119 | 120 | @pytest.fixture 121 | def accept_jsonp(): 122 | return _make_accept_header("application/json-p") 123 | 124 | 125 | @pytest.fixture(params=["*", "*/*"]) 126 | def accept_any(request): 127 | return _make_accept_header(request.param) 128 | -------------------------------------------------------------------------------- /src/pytest_flask/live_server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import multiprocessing 3 | import os 4 | import platform 5 | import signal 6 | import socket 7 | import time 8 | from multiprocessing import Process 9 | from typing import Any 10 | from typing import cast 11 | from typing import Protocol 12 | from typing import Union 13 | 14 | import pytest 15 | 16 | 17 | class _SupportsFlaskAppRun(Protocol): 18 | def run( 19 | self, 20 | host: Union[str, None] = None, 21 | port: Union[int, None] = None, 22 | debug: Union[bool, None] = None, 23 | load_dotenv: bool = True, 24 | **options: Any, 25 | ) -> None: ... 26 | 27 | 28 | # force 'fork' on macOS 29 | if platform.system() == "Darwin": 30 | multiprocessing = multiprocessing.get_context("fork") # type: ignore[assignment] 31 | 32 | 33 | class LiveServer: # pragma: no cover 34 | """The helper class used to manage a live server. Handles creation and 35 | stopping application in a separate process. 36 | 37 | :param app: The application to run. 38 | :param host: The host where to listen (default localhost). 39 | :param port: The port to run application. 40 | :param wait: The timeout after which test case is aborted if 41 | application is not started. 42 | """ 43 | 44 | def __init__( 45 | self, 46 | app: _SupportsFlaskAppRun, 47 | host: str, 48 | port: int, 49 | wait: int, 50 | clean_stop: bool = False, 51 | ): 52 | self.app = app 53 | self.port = port 54 | self.host = host 55 | self.wait = wait 56 | self.clean_stop = clean_stop 57 | self._process: Union[Process, None] = None 58 | 59 | def start(self) -> None: 60 | """Start application in a separate process.""" 61 | 62 | def worker(app: _SupportsFlaskAppRun, host: str, port: int) -> None: 63 | app.run(host=host, port=port, use_reloader=False, threaded=True) 64 | 65 | self._process = multiprocessing.Process( 66 | target=worker, args=(self.app, self.host, self.port) 67 | ) 68 | self._process.daemon = True 69 | self._process.start() 70 | 71 | keep_trying: bool = True 72 | start_time = time.time() 73 | while keep_trying: 74 | elapsed_time = time.time() - start_time 75 | if elapsed_time > self.wait: 76 | pytest.fail( 77 | "Failed to start the server after {!s} " 78 | "seconds.".format(self.wait) 79 | ) 80 | if self._is_ready(): 81 | keep_trying = False 82 | 83 | def _is_ready(self) -> bool: 84 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 85 | try: 86 | sock.connect((self.host, self.port)) 87 | except OSError: 88 | ret = False 89 | else: 90 | ret = True 91 | finally: 92 | sock.close() 93 | return ret 94 | 95 | def url(self, url: str = "") -> str: 96 | """Returns the complete url based on server options.""" 97 | return "http://{host!s}:{port!s}{url!s}".format( 98 | host=self.host, port=self.port, url=url 99 | ) 100 | 101 | def stop(self) -> None: 102 | """Stop application process.""" 103 | if self._process: 104 | if self.clean_stop and self._stop_cleanly(): 105 | return 106 | if self._process.is_alive(): 107 | # If it's still alive, kill it 108 | self._process.terminate() 109 | 110 | def _stop_cleanly(self, timeout: int = 5) -> bool: 111 | """Attempts to stop the server cleanly by sending a SIGINT 112 | signal and waiting for ``timeout`` seconds. 113 | 114 | :return: True if the server was cleanly stopped, False otherwise. 115 | """ 116 | if not self._process: 117 | return True 118 | 119 | try: 120 | os.kill(cast(int, self._process.pid), signal.SIGINT) 121 | self._process.join(timeout) 122 | return True 123 | except Exception as ex: 124 | logging.error("Failed to join the live server process: %r", ex) 125 | return False 126 | 127 | def __repr__(self): 128 | return "" % self.url() 129 | -------------------------------------------------------------------------------- /src/pytest_flask/plugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A py.test plugin which helps testing Flask applications. 4 | 5 | :copyright: (c) by Vital Kudzelka 6 | :license: MIT 7 | """ 8 | from typing import Any 9 | from typing import List 10 | from typing import Protocol 11 | from typing import Type 12 | from typing import TypeVar 13 | from typing import Union 14 | 15 | import pytest 16 | from _pytest.config import Config as _PytestConfig 17 | 18 | from .fixtures import accept_any 19 | from .fixtures import accept_json 20 | from .fixtures import accept_jsonp 21 | from .fixtures import accept_mimetype 22 | from .fixtures import client 23 | from .fixtures import client_class 24 | from .fixtures import config 25 | from .fixtures import live_server 26 | from .pytest_compat import getfixturevalue 27 | 28 | 29 | _Response = TypeVar("_Response") 30 | 31 | 32 | class _SupportsPytestFlaskEqual(Protocol): 33 | status_code: int 34 | 35 | def __eq__(self, other: Any) -> bool: ... 36 | 37 | def __ne__(self, other: Any) -> bool: ... 38 | 39 | 40 | class JSONResponse: 41 | """Mixin with testing helper methods for JSON responses.""" 42 | 43 | status_code: int 44 | 45 | def __eq__(self, other) -> bool: 46 | if isinstance(other, int): 47 | return self.status_code == other 48 | return super().__eq__(other) 49 | 50 | def __ne__(self, other) -> bool: 51 | return not self == other 52 | 53 | 54 | def pytest_assertrepr_compare( 55 | op: str, left: _SupportsPytestFlaskEqual, right: int 56 | ) -> Union[List[str], None]: 57 | if isinstance(left, JSONResponse) and op == "==" and isinstance(right, int): 58 | return [ 59 | "Mismatch in status code for response: {} != {}".format( 60 | left.status_code, 61 | right, 62 | ), 63 | f"Response status: {left.status_code}", 64 | ] 65 | return None 66 | 67 | 68 | def _make_test_response_class(response_class: Type[_Response]) -> Type[_Response]: 69 | """Extends the response class with special attribute to test JSON 70 | responses. Don't override user-defined `json` attribute if any. 71 | 72 | :param response_class: An original response class. 73 | """ 74 | if "json" in response_class.__dict__: 75 | return response_class 76 | 77 | return type(str(JSONResponse), (response_class, JSONResponse), {}) 78 | 79 | 80 | @pytest.fixture(autouse=True) 81 | def _monkeypatch_response_class(request, monkeypatch): 82 | """Set custom response class before test suite and restore the original 83 | after. Custom response has `json` property to easily test JSON responses:: 84 | 85 | @app.route('/ping') 86 | def ping(): 87 | return jsonify(ping='pong') 88 | 89 | def test_json(client): 90 | res = client.get(url_for('ping')) 91 | assert res.json == {'ping': 'pong'} 92 | 93 | """ 94 | 95 | if "app" not in request.fixturenames: 96 | return 97 | 98 | app = getfixturevalue(request, "app") 99 | monkeypatch.setattr( 100 | app, "response_class", _make_test_response_class(app.response_class) 101 | ) 102 | 103 | 104 | @pytest.fixture(autouse=True) 105 | def _push_request_context(request): 106 | """During tests execution request context has been pushed, e.g. `url_for`, 107 | `session`, etc. can be used in tests as is:: 108 | 109 | def test_app(app, client): 110 | assert client.get(url_for('myview')).status_code == 200 111 | 112 | """ 113 | if "app" not in request.fixturenames: 114 | return 115 | 116 | app = getfixturevalue(request, "app") 117 | 118 | # Get application bound to the live server if ``live_server`` fixture 119 | # is applied. Live server application has an explicit ``SERVER_NAME``, 120 | # so ``url_for`` function generates a complete URL for endpoint which 121 | # includes application port as well. 122 | if "live_server" in request.fixturenames: 123 | app = getfixturevalue(request, "live_server").app 124 | 125 | ctx = app.test_request_context() 126 | ctx.push() 127 | 128 | def teardown(): 129 | ctx.pop() 130 | 131 | request.addfinalizer(teardown) 132 | 133 | 134 | @pytest.fixture(autouse=True) 135 | def _configure_application(request, monkeypatch): 136 | """Use `pytest.mark.options` decorator to pass options to your application 137 | factory:: 138 | 139 | @pytest.mark.options(debug=False) 140 | def test_something(app): 141 | assert not app.debug, 'the application works not in debug mode!' 142 | 143 | """ 144 | if "app" not in request.fixturenames: 145 | return 146 | 147 | app = getfixturevalue(request, "app") 148 | for options in request.node.iter_markers("options"): 149 | for key, value in options.kwargs.items(): 150 | monkeypatch.setitem(app.config, key.upper(), value) 151 | 152 | 153 | def pytest_addoption(parser): 154 | group = parser.getgroup("flask") 155 | group.addoption( 156 | "--start-live-server", 157 | action="store_true", 158 | dest="start_live_server", 159 | default=True, 160 | help="start server automatically when live_server " 161 | "fixture is applied (enabled by default).", 162 | ) 163 | group.addoption( 164 | "--no-start-live-server", 165 | action="store_false", 166 | dest="start_live_server", 167 | help="don't start server automatically when live_server " "fixture is applied.", 168 | ) 169 | group.addoption( 170 | "--live-server-wait", 171 | action="store", 172 | dest="live_server_wait", 173 | default=5, 174 | type=float, 175 | help="the timeout after which test case is aborted if live server is " 176 | " not started.", 177 | ) 178 | group.addoption( 179 | "--live-server-clean-stop", 180 | action="store_true", 181 | dest="live_server_clean_stop", 182 | default=True, 183 | help="attempt to kill the live server cleanly.", 184 | ) 185 | group.addoption( 186 | "--no-live-server-clean-stop", 187 | action="store_false", 188 | dest="live_server_clean_stop", 189 | help="terminate the server forcefully after stop.", 190 | ) 191 | group.addoption( 192 | "--live-server-host", 193 | action="store", 194 | default="localhost", 195 | type=str, 196 | help="use a host where to listen (default localhost).", 197 | ) 198 | group.addoption( 199 | "--live-server-port", 200 | action="store", 201 | default=0, 202 | type=int, 203 | help="use a fixed port for the live_server fixture.", 204 | ) 205 | parser.addini( 206 | "live_server_scope", 207 | "modify the scope of the live_server fixture.", 208 | default="session", 209 | ) 210 | 211 | 212 | def pytest_configure(config: _PytestConfig) -> None: 213 | config.addinivalue_line( 214 | "markers", "app(options): pass options to your application factory" 215 | ) 216 | config.addinivalue_line("markers", "options: app config manipulation") 217 | -------------------------------------------------------------------------------- /src/pytest_flask/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytest-dev/pytest-flask/561a2bb46130a42791d84a1a0e8dfcd58aeddd13/src/pytest_flask/py.typed -------------------------------------------------------------------------------- /src/pytest_flask/pytest_compat.py: -------------------------------------------------------------------------------- 1 | def getfixturevalue(request, value): 2 | if hasattr(request, "getfixturevalue"): 3 | return request.getfixturevalue(value) 4 | 5 | return request.getfuncargvalue(value) # pragma: no cover 6 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from textwrap import dedent 3 | 4 | import pytest 5 | from flask import Flask 6 | from flask import jsonify 7 | 8 | from pytest_flask.fixtures import mimetype 9 | 10 | pytest_plugins = "pytester" 11 | 12 | 13 | @pytest.fixture(scope="session") 14 | def app(): 15 | app = Flask(__name__) 16 | app.config["SECRET_KEY"] = "42" 17 | 18 | @app.route("/") 19 | def index(): 20 | return app.response_class("OK") 21 | 22 | @app.route("/ping") 23 | def ping(): 24 | return jsonify(ping="pong") 25 | 26 | return app 27 | 28 | 29 | @pytest.fixture 30 | def appdir(testdir): 31 | app_root = testdir.tmpdir 32 | test_root = app_root.mkdir("tests") 33 | 34 | def create_test_module(code, filename="test_app.py"): 35 | f = test_root.join(filename) 36 | f.write(dedent(code), ensure=True) 37 | return f 38 | 39 | testdir.create_test_module = create_test_module 40 | 41 | testdir.create_test_module( 42 | """ 43 | import pytest 44 | 45 | from flask import Flask 46 | 47 | @pytest.fixture(scope='session') 48 | def app(): 49 | app = Flask(__name__) 50 | return app 51 | """, 52 | filename="conftest.py", 53 | ) 54 | return testdir 55 | -------------------------------------------------------------------------------- /tests/test_fixtures.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask import request 3 | from flask import url_for 4 | 5 | 6 | class TestFixtures: 7 | def test_config_access(self, config): 8 | assert config["SECRET_KEY"] == "42" 9 | 10 | def test_client(self, client): 11 | assert client.get(url_for("ping")).status == "200 OK" 12 | 13 | def test_accept_json(self, accept_json): 14 | assert accept_json == [("Accept", "application/json")] 15 | 16 | def test_accept_jsonp(self, accept_jsonp): 17 | assert accept_jsonp == [("Accept", "application/json-p")] 18 | 19 | def test_accept_mimetype(self, accept_mimetype): 20 | mimestrings = [[("Accept", "application/json")], [("Accept", "text/html")]] 21 | assert accept_mimetype in mimestrings 22 | 23 | def test_accept_any(self, accept_any): 24 | mimestrings = [[("Accept", "*")], [("Accept", "*/*")]] 25 | assert accept_any in mimestrings 26 | 27 | 28 | @pytest.mark.usefixtures("client_class") 29 | class TestClientClass: 30 | def test_client_attribute(self): 31 | assert hasattr(self, "client") 32 | assert self.client.get(url_for("ping")).json == {"ping": "pong"} 33 | -------------------------------------------------------------------------------- /tests/test_internal.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pytest_flask._internal import deprecated 4 | 5 | 6 | class TestInternal: 7 | def test_deprecation_decorator(self, appdir): 8 | @deprecated(reason="testing decorator") 9 | def deprecated_fun(): 10 | pass 11 | 12 | with pytest.warns(DeprecationWarning) as record: 13 | deprecated_fun() 14 | assert len(record) == 1 15 | assert record[0].message.args[0] == "testing decorator" 16 | -------------------------------------------------------------------------------- /tests/test_json_response.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask import url_for 3 | 4 | 5 | class TestJSONResponse: 6 | def test_json_response(self, client, accept_json): 7 | res = client.get(url_for("ping"), headers=accept_json) 8 | assert res.json == {"ping": "pong"} 9 | 10 | def test_json_response_compare_to_status_code(self, client, accept_json): 11 | assert client.get(url_for("ping"), headers=accept_json) == 200 12 | assert client.get("fake-route", headers=accept_json) == 404 13 | assert client.get("fake-route", headers=accept_json) != "404" 14 | res = client.get(url_for("ping"), headers=accept_json) 15 | assert res == res 16 | 17 | def test_mismatching_eq_comparison(self, client, accept_json): 18 | with pytest.raises(AssertionError, match=r"Mismatch in status code"): 19 | assert client.get("fake-route", headers=accept_json) == 200 20 | with pytest.raises(AssertionError, match=r"404 NOT FOUND"): 21 | assert client.get("fake-route", headers=accept_json) == "200" 22 | -------------------------------------------------------------------------------- /tests/test_live_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from flask import url_for 5 | 6 | 7 | pytestmark = pytest.mark.skipif(not hasattr(os, "fork"), reason="needs fork") 8 | 9 | 10 | class TestLiveServer: 11 | def test_init(self, live_server): 12 | assert live_server.port 13 | assert live_server.host == "localhost" 14 | assert live_server.url() == f"http://localhost:{live_server.port}" 15 | 16 | def test_server_is_alive(self, live_server): 17 | assert live_server._process 18 | assert live_server._process.is_alive() 19 | 20 | def test_server_listening(self, client, live_server): 21 | res = client.get(url_for("ping", _external=True)) 22 | assert res.status_code == 200 23 | assert b"pong" in res.data 24 | 25 | def test_url_for(self, live_server): 26 | assert ( 27 | url_for("ping", _external=True) 28 | == "http://localhost.localdomain:%s/ping" % live_server.port 29 | ) 30 | 31 | def test_set_application_server_name(self, live_server): 32 | assert ( 33 | live_server.app.config["SERVER_NAME"] 34 | == "localhost.localdomain:%d" % live_server.port 35 | ) 36 | 37 | def test_rewrite_application_server_name(self, appdir): 38 | appdir.create_test_module( 39 | """ 40 | import pytest 41 | @pytest.mark.options(server_name='example.com:5000') 42 | def test_a(live_server): 43 | assert live_server.app.config['SERVER_NAME'] == \\ 44 | 'example.com:%d' % live_server.port 45 | """ 46 | ) 47 | 48 | result = appdir.runpytest("-v", "-o", "live_server_scope=function") 49 | result.stdout.fnmatch_lines(["*PASSED*"]) 50 | assert result.ret == 0 51 | 52 | def test_prevent_starting_live_server(self, appdir): 53 | appdir.create_test_module( 54 | """ 55 | import pytest 56 | 57 | def test_a(live_server): 58 | assert live_server._process is None 59 | """ 60 | ) 61 | 62 | result = appdir.runpytest("-v", "--no-start-live-server") 63 | result.stdout.fnmatch_lines(["*passed*"]) 64 | assert result.ret == 0 65 | 66 | def test_start_live_server(self, appdir): 67 | appdir.create_test_module( 68 | """ 69 | import pytest 70 | 71 | def test_a(live_server): 72 | assert live_server._process 73 | assert live_server._process.is_alive() 74 | """ 75 | ) 76 | result = appdir.runpytest("-v", "--start-live-server") 77 | result.stdout.fnmatch_lines(["*passed*"]) 78 | assert result.ret == 0 79 | 80 | def test_stop_cleanly_join_exception(self, appdir, live_server, caplog): 81 | # timeout = 'a' here to force an exception when 82 | # attempting to self._process.join() 83 | assert not live_server._stop_cleanly(timeout="a") 84 | assert "Failed to join" in caplog.text 85 | 86 | @pytest.mark.parametrize("clean_stop", [True, False]) 87 | def test_clean_stop_live_server(self, appdir, monkeypatch, clean_stop): 88 | """Ensure the fixture is trying to cleanly stop the server. 89 | 90 | Because this is tricky to test, we are checking that the 91 | _stop_cleanly() internal function was called and reported success. 92 | """ 93 | from pytest_flask.fixtures import LiveServer 94 | 95 | original_stop_cleanly_func = LiveServer._stop_cleanly 96 | 97 | stop_cleanly_result = [] 98 | 99 | def mocked_stop_cleanly(*args, **kwargs): 100 | result = original_stop_cleanly_func(*args, **kwargs) 101 | stop_cleanly_result.append(result) 102 | return result 103 | 104 | monkeypatch.setattr(LiveServer, "_stop_cleanly", mocked_stop_cleanly) 105 | 106 | appdir.create_test_module( 107 | """ 108 | import pytest 109 | 110 | from flask import url_for 111 | 112 | def test_a(live_server, client): 113 | @live_server.app.route('/') 114 | def index(): 115 | return 'got it', 200 116 | 117 | live_server.start() 118 | 119 | res = client.get(url_for('index', _external=True)) 120 | assert res.status_code == 200 121 | assert b'got it' in res.data 122 | """ 123 | ) 124 | args = [] if clean_stop else ["--no-live-server-clean-stop"] 125 | result = appdir.runpytest_inprocess("-v", "--no-start-live-server", *args) 126 | result.stdout.fnmatch_lines("*1 passed*") 127 | if clean_stop: 128 | assert stop_cleanly_result == [True] 129 | else: 130 | assert stop_cleanly_result == [] 131 | 132 | def test_add_endpoint_to_live_server(self, appdir): 133 | appdir.create_test_module( 134 | """ 135 | import pytest 136 | 137 | from flask import url_for 138 | 139 | def test_a(live_server, client): 140 | @live_server.app.route('/new-endpoint') 141 | def new_endpoint(): 142 | return 'got it', 200 143 | 144 | live_server.start() 145 | 146 | res = client.get(url_for('new_endpoint', _external=True)) 147 | assert res.status_code == 200 148 | assert b'got it' in res.data 149 | """ 150 | ) 151 | result = appdir.runpytest("-v", "--no-start-live-server") 152 | result.stdout.fnmatch_lines(["*passed*"]) 153 | assert result.ret == 0 154 | 155 | @pytest.mark.skip("this test hangs in the original code") 156 | def test_concurrent_requests_to_live_server(self, appdir): 157 | appdir.create_test_module( 158 | """ 159 | import pytest 160 | 161 | from flask import url_for 162 | 163 | def test_concurrent_requests(live_server, client): 164 | @live_server.app.route('/one') 165 | def one(): 166 | res = client.get(url_for('two', _external=True)) 167 | return res.data 168 | 169 | @live_server.app.route('/two') 170 | def two(): 171 | return '42' 172 | 173 | live_server.start() 174 | 175 | res = client.get(url_for('one', _external=True)) 176 | assert res.status_code == 200 177 | assert b'42' in res.data 178 | """ 179 | ) 180 | result = appdir.runpytest("-v", "--no-start-live-server") 181 | result.stdout.fnmatch_lines(["*passed*"]) 182 | assert result.ret == 0 183 | 184 | @pytest.mark.parametrize("port", [5000, 5001]) 185 | def test_live_server_fixed_port(self, port, appdir): 186 | appdir.create_test_module( 187 | """ 188 | import pytest 189 | 190 | def test_port(live_server): 191 | assert live_server.port == %d 192 | """ 193 | % port 194 | ) 195 | result = appdir.runpytest("-v", "--live-server-port", str(port)) 196 | result.stdout.fnmatch_lines(["*PASSED*"]) 197 | assert result.ret == 0 198 | 199 | @pytest.mark.parametrize("host", ["127.0.0.1", "0.0.0.0"]) 200 | def test_live_server_fixed_host(self, host, appdir): 201 | appdir.create_test_module( 202 | """ 203 | import pytest 204 | 205 | def test_port(live_server): 206 | assert live_server.host == '%s' 207 | """ 208 | % host 209 | ) 210 | result = appdir.runpytest("-v", "--live-server-host", str(host)) 211 | result.stdout.fnmatch_lines(["*PASSED*"]) 212 | assert result.ret == 0 213 | 214 | def test_respect_wait_timeout(self, appdir): 215 | appdir.create_test_module( 216 | """ 217 | import pytest 218 | 219 | def test_should_fail(live_server): 220 | assert live_server._process.is_alive() 221 | """ 222 | ) 223 | result = appdir.runpytest("-v", "--live-server-wait=0.00000001") 224 | result.stdout.fnmatch_lines(["**ERROR**"]) 225 | assert result.ret == 1 226 | -------------------------------------------------------------------------------- /tests/test_markers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask import Flask 3 | 4 | 5 | @pytest.fixture(scope="session") 6 | def app(): 7 | app = Flask(__name__) 8 | app.config["SECRET_KEY"] = "42" 9 | return app 10 | 11 | 12 | class TestOptionMarker: 13 | @pytest.mark.options(debug=False) 14 | def test_not_debug_app(self, app): 15 | assert not app.debug, "Ensure the app not in debug mode" 16 | 17 | @pytest.mark.options(foo=42) 18 | def test_update_application_config(self, request, app, config): 19 | assert config["FOO"] == 42 20 | 21 | def test_application_config_teardown(self, config): 22 | assert "FOO" not in config 23 | -------------------------------------------------------------------------------- /tests/test_response_overwriting.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask import Flask 3 | from flask import jsonify 4 | from flask import url_for 5 | 6 | 7 | class TestResponseOverwriting: 8 | """ 9 | we overwrite the app fixture here so we can test 10 | _monkeypatch_response_class (an autouse fixture) 11 | will return the original response_class since a 12 | json @property is already present in response_class 13 | """ 14 | 15 | @pytest.fixture 16 | def app(self): 17 | app = Flask(__name__) 18 | app.config["SECRET_KEY"] = "42" 19 | 20 | class MyResponse(app.response_class): 21 | @property 22 | def json(self): 23 | return 49 24 | 25 | @app.route("/ping") 26 | def ping(): 27 | return jsonify(ping="pong") 28 | 29 | app.response_class = MyResponse 30 | 31 | return app 32 | 33 | def test_dont_rewrite_existing_implementation(self, accept_json, client): 34 | res = client.get(url_for("ping"), headers=accept_json) 35 | assert res.json == 49 36 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{38,39,310,311,312,linting} 4 | 5 | [pytest] 6 | norecursedirs = .git .tox env coverage docs 7 | pep8ignore = 8 | docs/conf.py ALL 9 | pep8maxlinelength = 79 10 | 11 | [testenv:dev] 12 | commands = 13 | envdir = venv 14 | deps= 15 | -rrequirements/main.txt 16 | -rrequirements/test.txt 17 | -rrequirements/docs.txt 18 | pre-commit>=1.11.0 19 | mypy>=1.6.1 20 | tox 21 | basepython = python3.8 22 | usedevelop = True 23 | 24 | [testenv] 25 | usedevelop = True 26 | deps = 27 | -rrequirements/main.txt 28 | -rrequirements/test.txt 29 | 30 | commands = 31 | coverage run -m pytest {posargs:tests} 32 | coverage combine 33 | coverage report 34 | 35 | [coverage:report] 36 | fail_under=90 37 | 38 | [coverage:run] 39 | source=pytest_flask 40 | concurrency=multiprocessing 41 | 42 | [tool:pytest] 43 | addopts = 44 | -v 45 | --basetemp={envtmpdir} 46 | --confcutdir=tests 47 | 48 | [testenv:pre] 49 | pip_pre=true 50 | usedevelop = {[testenv]usedevelop} 51 | deps = {[testenv]deps} 52 | commands = {[testenv]commands} 53 | 54 | [testenv:linting] 55 | skip_install = True 56 | basepython = python3.8 57 | deps = pre-commit>=1.11.0 58 | commands = pre-commit run --all-files --show-diff-on-failure {posargs:} 59 | 60 | [testenv:docs] 61 | changedir = docs 62 | skipsdist = True 63 | usedevelop = True 64 | deps = -r requirements/docs.txt 65 | commands = 66 | sphinx-build -W -b html . _build 67 | --------------------------------------------------------------------------------