199 lines
5.6 KiB
Python
199 lines
5.6 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
A py.test plugin which helps testing Flask applications.
|
|
|
|
:copyright: (c) by Vital Kudzelka
|
|
:license: MIT
|
|
"""
|
|
import sys
|
|
|
|
import pytest
|
|
from flask import json
|
|
from werkzeug.utils import cached_property
|
|
|
|
from .fixtures import accept_any
|
|
from .fixtures import accept_json
|
|
from .fixtures import accept_jsonp
|
|
from .fixtures import accept_mimetype
|
|
from .fixtures import client
|
|
from .fixtures import client_class
|
|
from .fixtures import config
|
|
from .fixtures import live_server
|
|
from .fixtures import request_ctx
|
|
from .pytest_compat import getfixturevalue
|
|
|
|
|
|
class JSONResponse:
|
|
"""Mixin with testing helper methods for JSON responses."""
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, int):
|
|
return self.status_code == other
|
|
return super().__eq__(other)
|
|
|
|
def __ne__(self, other):
|
|
return not self == other
|
|
|
|
|
|
def pytest_assertrepr_compare(op, left, right):
|
|
if isinstance(left, JSONResponse) and op == "==" and isinstance(right, int):
|
|
return [
|
|
"Mismatch in status code for response: {} != {}".format(
|
|
left.status_code,
|
|
right,
|
|
),
|
|
"Response status: {}".format(left.status),
|
|
]
|
|
return None
|
|
|
|
|
|
def _make_test_response_class(response_class):
|
|
"""Extends the response class with special attribute to test JSON
|
|
responses. Don't override user-defined `json` attribute if any.
|
|
|
|
:param response_class: An original response class.
|
|
"""
|
|
if "json" in response_class.__dict__:
|
|
return response_class
|
|
|
|
return type(str(JSONResponse), (response_class, JSONResponse), {})
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _monkeypatch_response_class(request, monkeypatch):
|
|
"""Set custom response class before test suite and restore the original
|
|
after. Custom response has `json` property to easily test JSON responses::
|
|
|
|
@app.route('/ping')
|
|
def ping():
|
|
return jsonify(ping='pong')
|
|
|
|
def test_json(client):
|
|
res = client.get(url_for('ping'))
|
|
assert res.json == {'ping': 'pong'}
|
|
|
|
"""
|
|
|
|
if "app" not in request.fixturenames:
|
|
return
|
|
|
|
app = getfixturevalue(request, "app")
|
|
monkeypatch.setattr(
|
|
app, "response_class", _make_test_response_class(app.response_class)
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _push_request_context(request):
|
|
"""During tests execution request context has been pushed, e.g. `url_for`,
|
|
`session`, etc. can be used in tests as is::
|
|
|
|
def test_app(app, client):
|
|
assert client.get(url_for('myview')).status_code == 200
|
|
|
|
"""
|
|
if "app" not in request.fixturenames:
|
|
return
|
|
|
|
app = getfixturevalue(request, "app")
|
|
|
|
# Get application bound to the live server if ``live_server`` fixture
|
|
# is applied. Live server application has an explicit ``SERVER_NAME``,
|
|
# so ``url_for`` function generates a complete URL for endpoint which
|
|
# includes application port as well.
|
|
if "live_server" in request.fixturenames:
|
|
app = getfixturevalue(request, "live_server").app
|
|
|
|
ctx = app.test_request_context()
|
|
ctx.push()
|
|
|
|
def teardown():
|
|
ctx.pop()
|
|
|
|
request.addfinalizer(teardown)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _configure_application(request, monkeypatch):
|
|
"""Use `pytest.mark.options` decorator to pass options to your application
|
|
factory::
|
|
|
|
@pytest.mark.options(debug=False)
|
|
def test_something(app):
|
|
assert not app.debug, 'the application works not in debug mode!'
|
|
|
|
"""
|
|
if "app" not in request.fixturenames:
|
|
return
|
|
|
|
app = getfixturevalue(request, "app")
|
|
for options in request.node.iter_markers("options"):
|
|
for key, value in options.kwargs.items():
|
|
monkeypatch.setitem(app.config, key.upper(), value)
|
|
|
|
|
|
def pytest_addoption(parser):
|
|
group = parser.getgroup("flask")
|
|
group.addoption(
|
|
"--start-live-server",
|
|
action="store_true",
|
|
dest="start_live_server",
|
|
default=True,
|
|
help="start server automatically when live_server "
|
|
"fixture is applied (enabled by default).",
|
|
)
|
|
group.addoption(
|
|
"--no-start-live-server",
|
|
action="store_false",
|
|
dest="start_live_server",
|
|
help="don't start server automatically when live_server " "fixture is applied.",
|
|
)
|
|
group.addoption(
|
|
"--live-server-wait",
|
|
action="store",
|
|
dest="live_server_wait",
|
|
default=5,
|
|
type=float,
|
|
help="the timeout after which test case is aborted if live server is "
|
|
" not started.",
|
|
)
|
|
group.addoption(
|
|
"--live-server-clean-stop",
|
|
action="store_true",
|
|
dest="live_server_clean_stop",
|
|
default=True,
|
|
help="attempt to kill the live server cleanly.",
|
|
)
|
|
group.addoption(
|
|
"--no-live-server-clean-stop",
|
|
action="store_false",
|
|
dest="live_server_clean_stop",
|
|
help="terminate the server forcefully after stop.",
|
|
)
|
|
group.addoption(
|
|
"--live-server-host",
|
|
action="store",
|
|
default="localhost",
|
|
type=str,
|
|
help="use a host where to listen (default localhost).",
|
|
)
|
|
group.addoption(
|
|
"--live-server-port",
|
|
action="store",
|
|
default=0,
|
|
type=int,
|
|
help="use a fixed port for the live_server fixture.",
|
|
)
|
|
parser.addini(
|
|
"live_server_scope",
|
|
"modify the scope of the live_server fixture.",
|
|
default="session",
|
|
)
|
|
|
|
|
|
def pytest_configure(config):
|
|
config.addinivalue_line(
|
|
"markers", "app(options): pass options to your application factory"
|
|
)
|
|
config.addinivalue_line("markers", "options: app config manipulation")
|