mirror of https://github.com/pallets/flask.git
add test_cli_runner for testing app.cli commands
This commit is contained in:
parent
79f34f1769
commit
cf5525f98a
|
@ -137,6 +137,8 @@ unreleased
|
|||
development server over HTTPS. (`#2606`_)
|
||||
- Added :data:`SESSION_COOKIE_SAMESITE` to control the ``SameSite``
|
||||
attribute on the session cookie. (`#2607`_)
|
||||
- Added :meth:`~flask.Flask.test_cli_runner` to create a Click runner
|
||||
that can invoke Flask CLI commands for testing. (`#2636`_)
|
||||
|
||||
.. _pallets/meta#24: https://github.com/pallets/meta/issues/24
|
||||
.. _#1421: https://github.com/pallets/flask/issues/1421
|
||||
|
@ -178,6 +180,7 @@ unreleased
|
|||
.. _#2581: https://github.com/pallets/flask/pull/2581
|
||||
.. _#2606: https://github.com/pallets/flask/pull/2606
|
||||
.. _#2607: https://github.com/pallets/flask/pull/2607
|
||||
.. _#2636: https://github.com/pallets/flask/pull/2636
|
||||
|
||||
|
||||
Version 0.12.2
|
||||
|
|
|
@ -188,6 +188,15 @@ Test Client
|
|||
:members:
|
||||
|
||||
|
||||
Test CLI Runner
|
||||
---------------
|
||||
|
||||
.. currentmodule:: flask.testing
|
||||
|
||||
.. autoclass:: FlaskCliRunner
|
||||
:members:
|
||||
|
||||
|
||||
Application Globals
|
||||
-------------------
|
||||
|
||||
|
|
|
@ -413,15 +413,17 @@ with ``get_json``.
|
|||
Testing CLI Commands
|
||||
--------------------
|
||||
|
||||
Click comes with `utilities for testing`_ your CLI commands.
|
||||
Click comes with `utilities for testing`_ your CLI commands. A
|
||||
:class:`~click.testing.CliRunner` runs commands in isolation and
|
||||
captures the output in a :class:`~click.testing.Result` object.
|
||||
|
||||
Use :meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` to call
|
||||
commands in the same way they would be called from the command line. The
|
||||
:class:`~click.testing.CliRunner` runs the command in isolation and
|
||||
captures the output in a :class:`~click.testing.Result` object. ::
|
||||
Flask provides :meth:`~flask.Flask.test_cli_runner` to create a
|
||||
:class:`~flask.testing.FlaskCliRunner` that passes the Flask app to the
|
||||
CLI automatically. Use its :meth:`~flask.testing.FlaskCliRunner.invoke`
|
||||
method to call commands in the same way they would be called from the
|
||||
command line. ::
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
|
||||
@app.cli.command('hello')
|
||||
@click.option('--name', default='World')
|
||||
|
@ -429,14 +431,22 @@ captures the output in a :class:`~click.testing.Result` object. ::
|
|||
click.echo(f'Hello, {name}!')
|
||||
|
||||
def test_hello():
|
||||
runner = CliRunner()
|
||||
runner = app.test_cli_runner()
|
||||
|
||||
# invoke the command directly
|
||||
result = runner.invoke(hello_command, ['--name', 'Flask'])
|
||||
assert 'Hello, Flask' in result.output
|
||||
|
||||
# or by name
|
||||
result = runner.invoke(args=['hello'])
|
||||
assert 'World' in result.output
|
||||
|
||||
In the example above, invoking the command by name is useful because it
|
||||
verifies that the command was correctly registered with the app.
|
||||
|
||||
If you want to test how your command parses parameters, without running
|
||||
the command, use the command's :meth:`~click.BaseCommand.make_context`
|
||||
method. This is useful for testing complex validation rules and custom
|
||||
types. ::
|
||||
the command, use its :meth:`~click.BaseCommand.make_context` method.
|
||||
This is useful for testing complex validation rules and custom types. ::
|
||||
|
||||
def upper(ctx, param, value):
|
||||
if value is not None:
|
||||
|
|
25
flask/app.py
25
flask/app.py
|
@ -311,6 +311,14 @@ class Flask(_PackageBoundObject):
|
|||
#: .. versionadded:: 0.7
|
||||
test_client_class = None
|
||||
|
||||
#: The :class:`~click.testing.CliRunner` subclass, by default
|
||||
#: :class:`~flask.testing.FlaskCliRunner` that is used by
|
||||
#: :meth:`test_cli_runner`. Its ``__init__`` method should take a
|
||||
#: Flask app object as the first argument.
|
||||
#:
|
||||
#: .. versionadded:: 1.0
|
||||
test_cli_runner_class = None
|
||||
|
||||
#: the session interface to use. By default an instance of
|
||||
#: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
|
||||
#:
|
||||
|
@ -983,6 +991,23 @@ class Flask(_PackageBoundObject):
|
|||
from flask.testing import FlaskClient as cls
|
||||
return cls(self, self.response_class, use_cookies=use_cookies, **kwargs)
|
||||
|
||||
def test_cli_runner(self, **kwargs):
|
||||
"""Create a CLI runner for testing CLI commands.
|
||||
See :ref:`testing-cli`.
|
||||
|
||||
Returns an instance of :attr:`test_cli_runner_class`, by default
|
||||
:class:`~flask.testing.FlaskCliRunner`. The Flask app object is
|
||||
passed as the first argument.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
"""
|
||||
cls = self.test_cli_runner_class
|
||||
|
||||
if cls is None:
|
||||
from flask.testing import FlaskCliRunner as cls
|
||||
|
||||
return cls(self, **kwargs)
|
||||
|
||||
def open_session(self, request):
|
||||
"""Creates or opens a new session. Default implementation stores all
|
||||
session data in a signed cookie. This requires that the
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
|
||||
import werkzeug
|
||||
from contextlib import contextmanager
|
||||
|
||||
from click.testing import CliRunner
|
||||
from flask.cli import ScriptInfo
|
||||
from werkzeug.test import Client, EnvironBuilder
|
||||
from flask import _request_ctx_stack
|
||||
from flask.json import dumps as json_dumps
|
||||
|
@ -193,3 +196,36 @@ class FlaskClient(Client):
|
|||
top = _request_ctx_stack.top
|
||||
if top is not None and top.preserved:
|
||||
top.pop()
|
||||
|
||||
|
||||
class FlaskCliRunner(CliRunner):
|
||||
"""A :class:`~click.testing.CliRunner` for testing a Flask app's
|
||||
CLI commands. Typically created using
|
||||
:meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.
|
||||
"""
|
||||
def __init__(self, app, **kwargs):
|
||||
self.app = app
|
||||
super(FlaskCliRunner, self).__init__(**kwargs)
|
||||
|
||||
def invoke(self, cli=None, args=None, **kwargs):
|
||||
"""Invokes a CLI command in an isolated environment. See
|
||||
:meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for
|
||||
full method documentation. See :ref:`testing-cli` for examples.
|
||||
|
||||
If the ``obj`` argument is not given, passes an instance of
|
||||
:class:`~flask.cli.ScriptInfo` that knows how to load the Flask
|
||||
app being tested.
|
||||
|
||||
:param cli: Command object to invoke. Default is the app's
|
||||
:attr:`~flask.app.Flask.cli` group.
|
||||
:param args: List of strings to invoke the command with.
|
||||
|
||||
:return: a :class:`~click.testing.Result` object.
|
||||
"""
|
||||
if cli is None:
|
||||
cli = self.app.cli
|
||||
|
||||
if 'obj' not in kwargs:
|
||||
kwargs['obj'] = ScriptInfo(create_app=lambda: self.app)
|
||||
|
||||
return super(FlaskCliRunner, self).invoke(cli, args, **kwargs)
|
||||
|
|
|
@ -8,15 +8,16 @@
|
|||
:copyright: © 2010 by the Pallets team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import click
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
import werkzeug
|
||||
|
||||
from flask._compat import text_type
|
||||
from flask.cli import ScriptInfo
|
||||
from flask.json import jsonify
|
||||
from flask.testing import make_test_environ_builder
|
||||
from flask.testing import make_test_environ_builder, FlaskCliRunner
|
||||
|
||||
|
||||
def test_environ_defaults_from_config(app, client):
|
||||
|
@ -335,3 +336,47 @@ def test_nosubdomain(app, client):
|
|||
|
||||
assert 200 == response.status_code
|
||||
assert b'xxx' == response.data
|
||||
|
||||
|
||||
def test_cli_runner_class(app):
|
||||
runner = app.test_cli_runner()
|
||||
assert isinstance(runner, FlaskCliRunner)
|
||||
|
||||
class SubRunner(FlaskCliRunner):
|
||||
pass
|
||||
|
||||
app.test_cli_runner_class = SubRunner
|
||||
runner = app.test_cli_runner()
|
||||
assert isinstance(runner, SubRunner)
|
||||
|
||||
|
||||
def test_cli_invoke(app):
|
||||
@app.cli.command('hello')
|
||||
def hello_command():
|
||||
click.echo('Hello, World!')
|
||||
|
||||
runner = app.test_cli_runner()
|
||||
# invoke with command name
|
||||
result = runner.invoke(args=['hello'])
|
||||
assert 'Hello' in result.output
|
||||
# invoke with command object
|
||||
result = runner.invoke(hello_command)
|
||||
assert 'Hello' in result.output
|
||||
|
||||
|
||||
def test_cli_custom_obj(app):
|
||||
class NS(object):
|
||||
called = False
|
||||
|
||||
def create_app():
|
||||
NS.called = True
|
||||
return app
|
||||
|
||||
@app.cli.command('hello')
|
||||
def hello_command():
|
||||
click.echo('Hello, World!')
|
||||
|
||||
script_info = ScriptInfo(create_app=create_app)
|
||||
runner = app.test_cli_runner()
|
||||
runner.invoke(hello_command, obj=script_info)
|
||||
assert NS.called
|
||||
|
|
Loading…
Reference in New Issue