2010-04-12 06:14:59 +08:00
|
|
|
.. _testing:
|
|
|
|
|
|
|
|
Testing Flask Applications
|
|
|
|
==========================
|
|
|
|
|
|
|
|
**Something that is untested is broken.**
|
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
The origin of this quote is unknown and while it is not entirely correct, it
|
|
|
|
is also not far from the truth. Untested applications make it hard to
|
2010-04-12 06:14:59 +08:00
|
|
|
improve existing code and developers of untested applications tend to
|
2010-06-28 11:24:37 +08:00
|
|
|
become pretty paranoid. If an application has automated tests, you can
|
2011-03-14 10:18:19 +08:00
|
|
|
safely make changes and instantly know if anything breaks.
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2014-07-24 23:03:56 +08:00
|
|
|
Flask provides a way to test your application by exposing the Werkzeug
|
2011-03-14 10:18:19 +08:00
|
|
|
test :class:`~werkzeug.test.Client` and handling the context locals for you.
|
2017-05-23 08:36:55 +08:00
|
|
|
You can then use that with your favourite testing solution.
|
|
|
|
|
|
|
|
In this documentation we will use the `pytest`_ package as the base
|
|
|
|
framework for our tests. You can install it with ``pip``, like so::
|
|
|
|
|
|
|
|
pip install pytest
|
|
|
|
|
|
|
|
.. _pytest:
|
|
|
|
https://pytest.org
|
2010-04-12 06:14:59 +08:00
|
|
|
|
|
|
|
The Application
|
|
|
|
---------------
|
|
|
|
|
2014-07-24 23:03:56 +08:00
|
|
|
First, we need an application to test; we will use the application from
|
|
|
|
the :ref:`tutorial`. If you don't have that application yet, get the
|
2017-05-23 08:36:55 +08:00
|
|
|
source code from `the examples`_.
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2010-04-16 08:03:45 +08:00
|
|
|
.. _the examples:
|
2016-04-04 05:11:38 +08:00
|
|
|
https://github.com/pallets/flask/tree/master/examples/flaskr/
|
2010-04-12 06:14:59 +08:00
|
|
|
|
|
|
|
The Testing Skeleton
|
|
|
|
--------------------
|
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
We begin by adding a tests directory under the application root. Then
|
|
|
|
create a Python file to store our tests (:file:`test_flaskr.py`). When we
|
|
|
|
format the filename like ``test_*.py``, it will be auto-discoverable by
|
|
|
|
pytest.
|
|
|
|
|
|
|
|
Next, we create a `pytest fixture`_ called
|
|
|
|
:func:`client` that configures
|
|
|
|
the application for testing and initializes a new database.::
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2010-04-21 02:03:03 +08:00
|
|
|
import os
|
2010-04-16 08:03:45 +08:00
|
|
|
import tempfile
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
import pytest
|
2017-05-24 02:50:14 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
from flaskr import flaskr
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-24 02:50:14 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
@pytest.fixture
|
2017-05-24 04:22:16 +08:00
|
|
|
def client():
|
2017-05-23 08:36:55 +08:00
|
|
|
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
|
|
|
flaskr.app.config['TESTING'] = True
|
|
|
|
client = flaskr.app.test_client()
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
with flaskr.app.app_context():
|
|
|
|
flaskr.init_db()
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 09:22:08 +08:00
|
|
|
yield client
|
2010-04-21 02:01:00 +08:00
|
|
|
|
2017-05-23 09:22:08 +08:00
|
|
|
os.close(db_fd)
|
|
|
|
os.unlink(flaskr.app.config['DATABASE'])
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
This client fixture will be called by each individual test. It gives us a
|
|
|
|
simple interface to the application, where we can trigger test requests to the
|
|
|
|
application. The client will also keep track of cookies for us.
|
2011-03-14 10:18:19 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
During setup, the ``TESTING`` config flag is activated. What
|
|
|
|
this does is disable error catching during request handling, so that
|
|
|
|
you get better error reports when performing test requests against the
|
|
|
|
application.
|
2010-04-16 08:03:45 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
Because SQLite3 is filesystem-based, we can easily use the :mod:`tempfile` module
|
2010-04-21 02:01:00 +08:00
|
|
|
to create a temporary database and initialize it. The
|
|
|
|
:func:`~tempfile.mkstemp` function does two things for us: it returns a
|
|
|
|
low-level file handle and a random file name, the latter we use as
|
|
|
|
database name. We just have to keep the `db_fd` around so that we can use
|
2010-04-21 04:46:36 +08:00
|
|
|
the :func:`os.close` function to close the file.
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-24 02:50:14 +08:00
|
|
|
To delete the database after the test, the fixture closes the file and removes
|
|
|
|
it from the filesystem.
|
2017-05-23 08:36:55 +08:00
|
|
|
|
2011-03-14 10:18:19 +08:00
|
|
|
If we now run the test suite, we should see the following output::
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
$ pytest
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
================ test session starts ================
|
|
|
|
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
|
|
|
collected 0 items
|
2010-04-21 04:46:36 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
=========== no tests ran in 0.07 seconds ============
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
Even though it did not run any actual tests, we already know that our ``flaskr``
|
2010-04-12 06:14:59 +08:00
|
|
|
application is syntactically valid, otherwise the import would have died
|
|
|
|
with an exception.
|
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
.. _pytest fixture:
|
|
|
|
https://docs.pytest.org/en/latest/fixture.html
|
|
|
|
|
2010-04-12 06:14:59 +08:00
|
|
|
The First Test
|
|
|
|
--------------
|
|
|
|
|
2014-07-24 23:03:56 +08:00
|
|
|
Now it's time to start testing the functionality of the application.
|
|
|
|
Let's check that the application shows "No entries here so far" if we
|
2017-05-23 08:36:55 +08:00
|
|
|
access the root of the application (``/``). To do this, we add a new
|
|
|
|
test function to :file:`test_flaskr.py`, like this::
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
def test_empty_db(client):
|
|
|
|
"""Start with a blank database."""
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
rv = client.get('/')
|
|
|
|
assert b'No entries here so far' in rv.data
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2014-07-24 23:03:56 +08:00
|
|
|
Notice that our test functions begin with the word `test`; this allows
|
2017-05-23 08:36:55 +08:00
|
|
|
`pytest`_ to automatically identify the function as a test to run.
|
2011-03-14 10:18:19 +08:00
|
|
|
|
2017-05-23 09:22:08 +08:00
|
|
|
By using ``client.get`` we can send an HTTP ``GET`` request to the application with
|
2014-07-24 23:03:56 +08:00
|
|
|
the given path. The return value will be a :class:`~flask.Flask.response_class` object.
|
2011-03-14 10:18:19 +08:00
|
|
|
We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect
|
2014-07-24 23:03:56 +08:00
|
|
|
the return value (as string) from the application. In this case, we ensure that
|
2011-03-14 10:18:19 +08:00
|
|
|
``'No entries here so far'`` is part of the output.
|
2010-04-16 08:03:45 +08:00
|
|
|
|
|
|
|
Run it again and you should see one passing test::
|
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
$ pytest -v
|
|
|
|
|
|
|
|
================ test session starts ================
|
|
|
|
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
|
|
|
collected 1 items
|
2010-04-16 08:03:45 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
tests/test_flaskr.py::test_empty_db PASSED
|
|
|
|
|
|
|
|
============= 1 passed in 0.10 seconds ==============
|
2010-04-16 08:03:45 +08:00
|
|
|
|
|
|
|
Logging In and Out
|
|
|
|
------------------
|
|
|
|
|
|
|
|
The majority of the functionality of our application is only available for
|
2011-03-14 10:18:19 +08:00
|
|
|
the administrative user, so we need a way to log our test client in and out
|
2014-07-24 23:03:56 +08:00
|
|
|
of the application. To do this, we fire some requests to the login and logout
|
|
|
|
pages with the required form data (username and password). And because the
|
2011-03-14 10:18:19 +08:00
|
|
|
login and logout pages redirect, we tell the client to `follow_redirects`.
|
2010-04-16 08:03:45 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
Add the following two functions to your :file:`test_flaskr.py` file::
|
2010-04-16 08:03:45 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
def login(client, username, password):
|
|
|
|
return client.post('/login', data=dict(
|
|
|
|
username=username,
|
|
|
|
password=password
|
|
|
|
), follow_redirects=True)
|
2010-04-16 08:03:45 +08:00
|
|
|
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
def logout(client):
|
|
|
|
return client.get('/logout', follow_redirects=True)
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2011-03-14 10:18:19 +08:00
|
|
|
Now we can easily test that logging in and out works and that it fails with
|
2017-05-23 08:36:55 +08:00
|
|
|
invalid credentials. Add this new test function::
|
|
|
|
|
2017-05-23 08:42:30 +08:00
|
|
|
def test_login_logout(client):
|
2017-05-24 02:50:14 +08:00
|
|
|
"""Make sure login and logout works."""
|
|
|
|
|
|
|
|
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
|
2017-05-23 08:36:55 +08:00
|
|
|
assert b'You were logged in' in rv.data
|
2017-05-24 02:50:14 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
rv = logout(client)
|
|
|
|
assert b'You were logged out' in rv.data
|
2017-05-24 02:50:14 +08:00
|
|
|
|
|
|
|
rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD'])
|
2017-05-23 08:36:55 +08:00
|
|
|
assert b'Invalid username' in rv.data
|
2017-05-24 02:50:14 +08:00
|
|
|
|
|
|
|
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x')
|
2017-05-23 08:36:55 +08:00
|
|
|
assert b'Invalid password' in rv.data
|
2010-04-16 08:03:45 +08:00
|
|
|
|
|
|
|
Test Adding Messages
|
|
|
|
--------------------
|
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
We should also test that adding messages works. Add a new test function
|
2010-04-16 08:03:45 +08:00
|
|
|
like this::
|
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
def test_messages(client):
|
2017-05-24 02:50:14 +08:00
|
|
|
"""Test that messages work."""
|
|
|
|
|
|
|
|
login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
|
2017-05-23 08:36:55 +08:00
|
|
|
rv = client.post('/add', data=dict(
|
2010-04-16 08:03:45 +08:00
|
|
|
title='<Hello>',
|
|
|
|
text='<strong>HTML</strong> allowed here'
|
|
|
|
), follow_redirects=True)
|
2016-08-20 10:01:13 +08:00
|
|
|
assert b'No entries here so far' not in rv.data
|
|
|
|
assert b'<Hello>' in rv.data
|
|
|
|
assert b'<strong>HTML</strong> allowed here' in rv.data
|
2010-04-16 08:03:45 +08:00
|
|
|
|
2010-04-20 12:25:51 +08:00
|
|
|
Here we check that HTML is allowed in the text but not in the title,
|
2010-04-16 08:03:45 +08:00
|
|
|
which is the intended behavior.
|
|
|
|
|
|
|
|
Running that should now give us three passing tests::
|
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
$ pytest -v
|
|
|
|
|
|
|
|
================ test session starts ================
|
|
|
|
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
|
|
|
collected 3 items
|
|
|
|
|
|
|
|
tests/test_flaskr.py::test_empty_db PASSED
|
|
|
|
tests/test_flaskr.py::test_login_logout PASSED
|
|
|
|
tests/test_flaskr.py::test_messages PASSED
|
2010-05-30 02:06:12 +08:00
|
|
|
|
2017-05-23 08:36:55 +08:00
|
|
|
============= 3 passed in 0.23 seconds ==============
|
2010-04-12 06:14:59 +08:00
|
|
|
|
2010-04-16 08:03:45 +08:00
|
|
|
For more complex tests with headers and status codes, check out the
|
2011-03-14 10:18:19 +08:00
|
|
|
`MiniTwit Example`_ from the sources which contains a larger test
|
2010-04-16 08:03:45 +08:00
|
|
|
suite.
|
2010-04-12 06:14:59 +08:00
|
|
|
|
|
|
|
.. _MiniTwit Example:
|
2016-04-04 05:11:38 +08:00
|
|
|
https://github.com/pallets/flask/tree/master/examples/minitwit/
|
2010-05-02 17:45:40 +08:00
|
|
|
|
|
|
|
Other Testing Tricks
|
|
|
|
--------------------
|
|
|
|
|
2011-03-14 10:18:19 +08:00
|
|
|
Besides using the test client as shown above, there is also the
|
|
|
|
:meth:`~flask.Flask.test_request_context` method that can be used
|
2014-11-05 12:39:54 +08:00
|
|
|
in combination with the ``with`` statement to activate a request context
|
2011-03-14 10:18:19 +08:00
|
|
|
temporarily. With this you can access the :class:`~flask.request`,
|
2010-05-02 17:45:40 +08:00
|
|
|
:class:`~flask.g` and :class:`~flask.session` objects like in view
|
2011-03-14 10:18:19 +08:00
|
|
|
functions. Here is a full example that demonstrates this approach::
|
2010-05-02 17:45:40 +08:00
|
|
|
|
2015-03-13 11:32:08 +08:00
|
|
|
import flask
|
2017-04-14 07:32:44 +08:00
|
|
|
|
2010-05-02 17:45:40 +08:00
|
|
|
app = flask.Flask(__name__)
|
|
|
|
|
|
|
|
with app.test_request_context('/?name=Peter'):
|
|
|
|
assert flask.request.path == '/'
|
|
|
|
assert flask.request.args['name'] == 'Peter'
|
|
|
|
|
2011-03-14 10:18:19 +08:00
|
|
|
All the other objects that are context bound can be used in the same
|
|
|
|
way.
|
2010-05-28 03:17:25 +08:00
|
|
|
|
|
|
|
If you want to test your application with different configurations and
|
|
|
|
there does not seem to be a good way to do that, consider switching to
|
|
|
|
application factories (see :ref:`app-factories`).
|
2010-06-03 21:26:07 +08:00
|
|
|
|
2011-06-18 03:53:11 +08:00
|
|
|
Note however that if you are using a test request context, the
|
2016-04-02 05:12:25 +08:00
|
|
|
:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request`
|
|
|
|
functions are not called automatically. However
|
2011-06-18 03:53:11 +08:00
|
|
|
:meth:`~flask.Flask.teardown_request` functions are indeed executed when
|
2014-11-05 12:39:54 +08:00
|
|
|
the test request context leaves the ``with`` block. If you do want the
|
2011-06-18 03:53:11 +08:00
|
|
|
:meth:`~flask.Flask.before_request` functions to be called as well, you
|
|
|
|
need to call :meth:`~flask.Flask.preprocess_request` yourself::
|
|
|
|
|
|
|
|
app = flask.Flask(__name__)
|
|
|
|
|
|
|
|
with app.test_request_context('/?name=Peter'):
|
|
|
|
app.preprocess_request()
|
|
|
|
...
|
|
|
|
|
|
|
|
This can be necessary to open database connections or something similar
|
|
|
|
depending on how your application was designed.
|
|
|
|
|
2011-07-10 19:34:21 +08:00
|
|
|
If you want to call the :meth:`~flask.Flask.after_request` functions you
|
|
|
|
need to call into :meth:`~flask.Flask.process_response` which however
|
|
|
|
requires that you pass it a response object::
|
|
|
|
|
|
|
|
app = flask.Flask(__name__)
|
|
|
|
|
|
|
|
with app.test_request_context('/?name=Peter'):
|
|
|
|
resp = Response('...')
|
|
|
|
resp = app.process_response(resp)
|
|
|
|
...
|
|
|
|
|
|
|
|
This in general is less useful because at that point you can directly
|
|
|
|
start using the test client.
|
|
|
|
|
2013-06-05 17:02:33 +08:00
|
|
|
.. _faking-resources:
|
2010-06-03 21:26:07 +08:00
|
|
|
|
2013-06-05 16:58:28 +08:00
|
|
|
Faking Resources and Context
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
.. versionadded:: 0.10
|
|
|
|
|
|
|
|
A very common pattern is to store user authorization information and
|
|
|
|
database connections on the application context or the :attr:`flask.g`
|
|
|
|
object. The general pattern for this is to put the object on there on
|
|
|
|
first usage and then to remove it on a teardown. Imagine for instance
|
|
|
|
this code to get the current user::
|
|
|
|
|
|
|
|
def get_user():
|
|
|
|
user = getattr(g, 'user', None)
|
|
|
|
if user is None:
|
|
|
|
user = fetch_current_user_from_database()
|
|
|
|
g.user = user
|
|
|
|
return user
|
|
|
|
|
|
|
|
For a test it would be nice to override this user from the outside without
|
2015-06-10 16:58:49 +08:00
|
|
|
having to change some code. This can be accomplished with
|
2013-06-05 16:58:28 +08:00
|
|
|
hooking the :data:`flask.appcontext_pushed` signal::
|
|
|
|
|
|
|
|
from contextlib import contextmanager
|
2015-01-13 10:03:14 +08:00
|
|
|
from flask import appcontext_pushed, g
|
2013-06-05 16:58:28 +08:00
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def user_set(app, user):
|
|
|
|
def handler(sender, **kwargs):
|
|
|
|
g.user = user
|
|
|
|
with appcontext_pushed.connected_to(handler, app):
|
|
|
|
yield
|
|
|
|
|
|
|
|
And then to use it::
|
|
|
|
|
|
|
|
from flask import json, jsonify
|
|
|
|
|
|
|
|
@app.route('/users/me')
|
|
|
|
def users_me():
|
|
|
|
return jsonify(username=g.user.username)
|
|
|
|
|
|
|
|
with user_set(app, my_user):
|
|
|
|
with app.test_client() as c:
|
|
|
|
resp = c.get('/users/me')
|
|
|
|
data = json.loads(resp.data)
|
|
|
|
self.assert_equal(data['username'], my_user.username)
|
|
|
|
|
|
|
|
|
2010-06-03 21:26:07 +08:00
|
|
|
Keeping the Context Around
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
.. versionadded:: 0.4
|
|
|
|
|
2011-03-14 10:18:19 +08:00
|
|
|
Sometimes it is helpful to trigger a regular request but still keep the
|
2010-06-03 21:26:07 +08:00
|
|
|
context around for a little longer so that additional introspection can
|
|
|
|
happen. With Flask 0.4 this is possible by using the
|
2014-11-05 12:39:54 +08:00
|
|
|
:meth:`~flask.Flask.test_client` with a ``with`` block::
|
2010-06-03 21:26:07 +08:00
|
|
|
|
|
|
|
app = flask.Flask(__name__)
|
|
|
|
|
|
|
|
with app.test_client() as c:
|
2010-06-11 07:37:35 +08:00
|
|
|
rv = c.get('/?tequila=42')
|
|
|
|
assert request.args['tequila'] == '42'
|
2010-06-03 21:26:07 +08:00
|
|
|
|
2011-03-14 10:18:19 +08:00
|
|
|
If you were to use just the :meth:`~flask.Flask.test_client` without
|
2014-11-05 12:39:54 +08:00
|
|
|
the ``with`` block, the ``assert`` would fail with an error because `request`
|
2011-03-14 10:18:19 +08:00
|
|
|
is no longer available (because you are trying to use it outside of the actual request).
|
2011-08-25 23:13:43 +08:00
|
|
|
|
2013-06-05 16:58:28 +08:00
|
|
|
|
2011-08-25 23:13:43 +08:00
|
|
|
Accessing and Modifying Sessions
|
|
|
|
--------------------------------
|
|
|
|
|
|
|
|
.. versionadded:: 0.8
|
|
|
|
|
|
|
|
Sometimes it can be very helpful to access or modify the sessions from the
|
2011-08-26 15:13:54 +08:00
|
|
|
test client. Generally there are two ways for this. If you just want to
|
2011-08-25 23:13:43 +08:00
|
|
|
ensure that a session has certain keys set to certain values you can just
|
|
|
|
keep the context around and access :data:`flask.session`::
|
|
|
|
|
|
|
|
with app.test_client() as c:
|
|
|
|
rv = c.get('/')
|
|
|
|
assert flask.session['foo'] == 42
|
|
|
|
|
|
|
|
This however does not make it possible to also modify the session or to
|
|
|
|
access the session before a request was fired. Starting with Flask 0.8 we
|
|
|
|
provide a so called “session transaction” which simulates the appropriate
|
|
|
|
calls to open a session in the context of the test client and to modify
|
|
|
|
it. At the end of the transaction the session is stored. This works
|
|
|
|
independently of the session backend used::
|
|
|
|
|
|
|
|
with app.test_client() as c:
|
|
|
|
with c.session_transaction() as sess:
|
|
|
|
sess['a_key'] = 'a value'
|
|
|
|
|
|
|
|
# once this is reached the session was stored
|
|
|
|
|
|
|
|
Note that in this case you have to use the ``sess`` object instead of the
|
|
|
|
:data:`flask.session` proxy. The object however itself will provide the
|
|
|
|
same interface.
|
2015-04-02 08:13:48 +08:00
|
|
|
|
|
|
|
|
|
|
|
Testing JSON APIs
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
.. versionadded:: 1.0
|
|
|
|
|
2015-04-06 19:15:07 +08:00
|
|
|
Flask has great support for JSON, and is a popular choice for building REST
|
2015-04-02 08:34:51 +08:00
|
|
|
APIs. Testing both JSON requests and responses using the test client is very
|
2015-04-13 06:34:03 +08:00
|
|
|
convenient::
|
2015-04-02 08:34:51 +08:00
|
|
|
|
2015-04-06 19:15:07 +08:00
|
|
|
from flask import jsonify
|
2015-04-02 08:34:51 +08:00
|
|
|
|
|
|
|
@app.route('/api/auth')
|
|
|
|
def auth():
|
2015-04-06 19:50:29 +08:00
|
|
|
json_data = request.get_json()
|
|
|
|
email = json_data['email']
|
|
|
|
password = json_data['password']
|
2015-04-13 06:34:03 +08:00
|
|
|
|
2015-04-02 08:34:51 +08:00
|
|
|
return jsonify(token=generate_token(email, password))
|
|
|
|
|
|
|
|
with app.test_client() as c:
|
|
|
|
email = 'john@example.com'
|
|
|
|
password = 'secret'
|
2015-04-06 19:15:07 +08:00
|
|
|
resp = c.post('/api/auth', json={'login': email, 'password': password})
|
2015-04-13 06:34:03 +08:00
|
|
|
|
2015-04-06 19:50:29 +08:00
|
|
|
json_data = resp.get_json()
|
|
|
|
assert verify_token(email, json_data['token'])
|
2015-04-02 08:34:51 +08:00
|
|
|
|
|
|
|
Note that if the ``json`` argument is provided then the test client will put
|
2015-04-06 19:15:07 +08:00
|
|
|
JSON-serialized data in the request body, and also set the
|
|
|
|
``Content-Type: application/json`` HTTP header.
|