Permalink
Browse files

Added support for deferred context cleanup. test_client users can now…

… access the context locals after the actual request if the client is used with a with-block. This fixes #59.
  • Loading branch information...
1 parent 9373608 commit bc00fd1e83f23f57dd6a765b6a4bab2394584ae6 @mitsuhiko mitsuhiko committed Jun 3, 2010
Showing with 111 additions and 3 deletions.
  1. +3 −0 CHANGES
  2. +17 −0 docs/api.rst
  3. +24 −0 docs/testing.rst
  4. +36 −3 flask.py
  5. +31 −0 tests/flask_tests.py
View
3 CHANGES
@@ -13,6 +13,9 @@ Release date to be announced, codename to be selected.
- :meth:`~flask.Flask.after_request` handlers are now also invoked
if the request dies with an exception and an error handling page
kicks in.
+- test client has not the ability to preserve the request context
+ for a little longer. This can also be used to trigger custom
+ requests that do not pop the request stack for testing.
Version 0.3.1
-------------
View
17 docs/api.rst
@@ -300,3 +300,20 @@ Useful Internals
all the context local objects used in Flask. This is a documented
instance and can be used by extensions and application code but the
use is discouraged in general.
+
+ .. versionchanged:: 0.4
+
+ The request context is automatically popped at the end of the request
+ for you. In debug mode the request context is kept around if
+ exceptions happen so that interactive debuggers have a chance to
+ introspect the data. With 0.4 this can also be forced for requests
+ that did not fail and outside of `DEBUG` mode. By setting
+ ``'flask._preserve_context'`` to `True` on the WSGI environment the
+ context will not pop itself at the end of the request. This is used by
+ the :meth:`~flask.Flask.test_client` for example to implement the
+ deferred cleanup functionality.
+
+ You might find this helpful for unittests where you need the
+ information from the context local around for a little longer. Make
+ sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
+ that situation, otherwise your unittests will leak memory.
View
24 docs/testing.rst
@@ -218,3 +218,27 @@ All the other objects that are context bound can be used the same.
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`).
+
+
+Keeping the Context Around
+--------------------------
+
+.. versionadded:: 0.4
+
+Sometimes it can be helpful to trigger a regular request but keep the
+context around for a little longer so that additional introspection can
+happen. With Flask 0.4 this is possible by using the
+:meth:`~flask.Flask.test_client` with a `with` block::
+
+ app = flask.Flask(__name__)
+
+ with app.test_client() as c:
+ rv = c.get('/?foo=42')
+ assert request.args['foo'] == '42'
+
+If you would just be using the :meth:`~flask.Flask.test_client` without
+the `with` block, the `assert` would fail with an error because `request`
+is no longer available (because used outside of an actual request).
+Keep in mind however that :meth:`~flask.Flask.after_request` functions
+are already called at that point so your database connection and
+everything involved is probably already closed down.
View
39 flask.py
@@ -163,8 +163,10 @@ def __enter__(self):
def __exit__(self, exc_type, exc_value, tb):
# do not pop the request stack if we are in debug mode and an
# exception happened. This will allow the debugger to still
- # access the request object in the interactive shell.
- if tb is None or not self.app.debug:
+ # access the request object in the interactive shell. Furthermore
+ # the context can be force kept alive for the test client.
+ if not self.request.environ.get('flask._preserve_context') and \
+ (tb is None or not self.app.debug):
self.pop()
@@ -1021,9 +1023,40 @@ def run(self, host='127.0.0.1', port=5000, **options):
def test_client(self):
"""Creates a test client for this application. For information
about unit testing head over to :ref:`testing`.
+
+ The test client can be used in a `with` block to defer the closing down
+ of the context until the end of the `with` block. This is useful if
+ you want to access the context locals for testing::
+
+ with app.test_client() as c:
+ rv = c.get('/?foo=42')
+ assert request.args['foo'] == '42'
+
+ .. versionchanged:: 0.4
+ added support for `with` block usage for the client.
"""
from werkzeug import Client
- return Client(self, self.response_class, use_cookies=True)
+ class FlaskClient(Client):
+ preserve_context = context_preserved = False
+ def open(self, *args, **kwargs):
+ if self.context_preserved:
+ _request_ctx_stack.pop()
+ self.context_preserved = False
+ kwargs.setdefault('environ_overrides', {}) \
+ ['flask._preserve_context'] = self.preserve_context
+ old = _request_ctx_stack.top
+ try:
+ return Client.open(self, *args, **kwargs)
+ finally:
+ self.context_preserved = _request_ctx_stack.top is not old
+ def __enter__(self):
+ self.preserve_context = True
+ return self
+ def __exit__(self, exc_type, exc_value, tb):
+ self.preserve_context = False
+ if self.context_preserved:
+ _request_ctx_stack.pop()
+ return FlaskClient(self, self.response_class, use_cookies=True)
def open_session(self, request):
"""Creates or opens a new session. Default implementation stores all
View
31 tests/flask_tests.py
@@ -58,6 +58,7 @@ def meh():
assert index() == 'Hello World!'
with app.test_request_context('/meh'):
assert meh() == 'http://localhost/meh'
+ assert flask._request_ctx_stack.top is None
def test_manual_context_binding(self):
app = flask.Flask(__name__)
@@ -76,6 +77,36 @@ def index():
else:
assert 0, 'expected runtime error'
+ def test_test_client_context_binding(self):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ flask.g.value = 42
+ return 'Hello World!'
+
+ @app.route('/other')
+ def other():
+ 1/0
+
+ with app.test_client() as c:
+ resp = c.get('/')
+ assert flask.g.value == 42
+ assert resp.data == 'Hello World!'
+ assert resp.status_code == 200
+
+ resp = c.get('/other')
+ assert not hasattr(flask.g, 'value')
+ assert 'Internal Server Error' in resp.data
+ assert resp.status_code == 500
+ flask.g.value = 23
+
+ try:
+ flask.g.value
+ except (AttributeError, RuntimeError):
+ pass
+ else:
+ raise AssertionError('some kind of exception expected')
+
class BasicFunctionalityTestCase(unittest.TestCase):

0 comments on commit bc00fd1

Please sign in to comment.