36
6.4 Controlling Autoescaping
Autoescapingisthe concept of automatically escapingspecial charactersofyou. Spe-
cial characters in the sense of HTML(or XML, andthusXHTML) are &,>, <,"aswell
as’.Because thesecharacterscarryspecificmeaningsin documentsontheirown you
havetoreplacethembysocalled“entities”ifyouwanttousethemfortext. Notdoing
sowouldnotonlycauseuserfrustrationbytheinabilitytousethesecharactersintext,
but canalsoleadtosecurityproblems. (seeCross-SiteScripting(XSS))
Sometimes however you will needto disable autoescaping in templates. Thiscan be
the case if you want to explicitly inject HTML into pages, for example if they come
fromasystemthatgeneratesecureHTMLlike amarkdowntoHTMLconverter.
Thereare three waystoaccomplishthat:
• InthePythoncode,wraptheHTMLstringinaMarkupobjectbefore passingitto
the template. Thisisin general the recommendedway.
• Insidethe template,use the |safefiltertoexplicitlymarkastringassafe HTML
({{ myvariable|safe }})
• Temporarilydisabletheautoescapesystemaltogether.
To disable the autoescape system in templates, you can use the {% autoescape %}
block:
{% autoescape false %}
<p>autoescaping is disabled here
<p>{{ will_not_be_escaped d }}
{% endautoescape %}
Whenever you do this, please be very cautious about the variables you are using in
thisblock.
6.5 Registering Filters
If you want to register your own filters in Jinja2 you have two ways to do that.
You can either put them by hand into the jinja_env of the application or use the
template_filter()decorator.
The twofollowingexamplesworkthe same andbothreverse anobject:
@app.template_filter(reverse)
def reverse_filter(s):
return s[::-1]
def reverse_filter(s):
return s[::-1]
app.jinja_env.filters[reverse] = reverse_filter
41
30
In case ofthe decoratortheargumentisoptional ifyouwanttouse the functionname
as name of the filter. Once registered, you can use the filter in your templates in the
same way as Jinja2’s builtin filters, for example if you have a Python list in context
calledmylist:
{% for x in mylist | reverse %}
{% endfor %}
6.6 Context Processors
Toinjectnewvariablesautomaticallyintothecontextofatemplate,contextprocessors
exist in Flask. Context processors run before the template is rendered and have the
abilitytoinjectnewvaluesintothe templatecontext. Acontextprocessorisafunction
that returnsadictionary. The keysandvaluesofthisdictionaryare thenmergedwith
the template context,for all templatesintheapp:
@app.context_processor
def inject_user():
return dict(user=g.user)
The context processor above makes a variable called user available in the template
with the value of g.user. Thisexample isnot very interestingbecause gisavailable in
templatesanyways,butitgivesanideahowthisworks.
Variablesare notlimitedtovalues;acontext processor canalsomake functionsavail-
able totemplates(sincePythonallowspassingaroundfunctions):
@app.context_processor
def utility_processor():
def format_price(amount, currency=u):
return u{0:.2f}{1}.format(amount, currency)
return dict(format_price=format_price)
Thecontextprocessorabovemakestheformat_pricefunctionavailabletoalltemplates:
{{ format_price(0.33) }}
You could also build format_price as a template filter (see Registering Filters), but this
demonstrateshowtopassfunctionsinacontextprocessor.
42
29
CHAPTER
SEVEN
TESTING FLASK APPLICATIONS
Somethingthatisuntestedisbroken.
The origin of this quote is unknown and while it isnot entirelycorrect, it is also not
far from the truth. Untestedapplicationsmake it hardto improve existing code and
developersof untestedapplicationstendtobecome prettyparanoid. Ifanapplication
has automated tests, you can safely make changes and instantly know if anything
breaks.
Flask provides a waytotest your application by exposing the Werkzeug testClient
and handling the context locals for you. You can then use that with your favourite
testingsolution. In thisdocumentation we will use theunittest package thatcomes
pre-installedwithPython.
7.1 The Application
First, we need an application to test; we will use the application from the Tutorial. If
youdon’thavethatapplicationyet,get the sourcesfromtheexamples.
7.2 The Testing Skeleton
In order totest the application, we add asecondmodule (flaskr_tests.py) and create a
unittestskeletonthere:
import os
import flaskr
import unittest
import tempfile
class FlaskrTestCase(unittest.TestCase):
def setUp(self):
self.db_fd, flaskr.app.config[DATABASE] = tempfile.mkstemp()
flaskr.app.config[TESTING] = True
self.app = flaskr.app.test_client()
43
36
flaskr.init_db()
def tearDown(self):
os.close(self.db_fd)
os.unlink(flaskr.app.config[DATABASE])
if __name__ == __main__:
unittest.main()
ThecodeinthesetUp()methodcreatesanewtestclientandinitializesanewdatabase.
This function is called before each individual test function is run. To delete the
database after the test, we close the file and remove it from the filesystem in the
tearDown()method. AdditionallyduringsetuptheTESTINGconfigflagisactivated.
What it doesis disabling the error catching during request handling so that you get
better error reportswhenperformingtestrequestsagainstthe application.
This test client will give us a simple interface to the application. We can trigger test
requeststotheapplication,andthe clientwillalsokeeptrackof cookiesforus.
BecauseSQLite3isfilesystem-basedwecan easilyusethetempfile moduletocreate a
temporarydatabaseandinitializeit. Themkstemp()functiondoestwothingsforus: it
returnsa low-level file handle and arandom file name, the latter we use as database
name. We just have tokeep thedb_fdaroundsothat we canuse theos.close()func-
tiontoclosethe file.
If we nowrunthe testsuite,weshouldseethefollowingoutput:
$ python flaskr_tests.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Even thoughit didnot runanyactual tests, we alreadyknow that our flaskr applica-
tionissyntacticallyvalid,otherwise the importwouldhavediedwithanexception.
7.3 The First Test
Now it’stime tostart testingthe functionality of the application. Let’scheckthat the
applicationshows“Noentrieshere sofar”if we accesstherootofthe application(/).
Todothis,weaddanew test methodtoour class,like this:
class FlaskrTestCase(unittest.TestCase):
def setUp(self):
self.db_fd, flaskr.app.config[DATABASE] = tempfile.mkstemp()
self.app = flaskr.app.test_client()
flaskr.init_db()
44
38
def tearDown(self):
os.close(self.db_fd)
os.unlink(flaskr.app.config[DATABASE])
def test_empty_db(self):
rv = self.app.get(/)
assert No entries here e so far in rv.data
Notice that our test functionsbegin with the word test; this allowsunittest to auto-
maticallyidentifythemethodasatesttorun.
By using self.app.get we can send an HTTP GET request to the application with the
given path. The return value will be a response_class object. We can now use the
dataattributetoinspectthereturnvalue(asstring)fromtheapplication.Inthiscase,
we ensure that’No entries here so far’ispartoftheoutput.
Run itagainandyoushouldsee one passingtest:
$ python flaskr_tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.034s
OK
7.4 Logging In and Out
The majorityof the functionality ofour application is only available for the adminis-
trative user,soweneedawaytologourtestclientinandoutofthe application. Todo
this,we fire some requeststothe loginandlogout pageswith the requiredform data
(username andpassword). And because the login andlogout pages redirect, we tell
the client tofollow_redirects.
Addthe followingtwomethodstoyourFlaskrTestCase class:
def login(self, username, , password):
return self.app.post(/login, data=dict(
username=username,
password=password
), follow_redirects=True)
def logout(self):
return self.app.get(/logout, follow_redirects=True)
Now we can easily test that logging in and out works and that it fails with invalid
credentials. Addthisnewtesttotheclass:
def test_login_logout(self):
rv = self.login(admin, default)
assert You were e logged in in rv.data
45
37
rv = self.logout()
assert You were e logged out in rv.data
rv = self.login(adminx, , default)
assert Invalid username in n rv.data
rv = self.login(admin, defaultx)
assert Invalid password in n rv.data
7.5 Test Adding Messages
We shouldalsotest thataddingmessagesworks. Addanew test methodlike this:
def test_messages(self):
self.login(admin, default)
rv = self.app.post(/add, data=dict(
title=<Hello>,
text=<strong>HTML</strong> allowed here
), follow_redirects=True)
assert No entries here e so o far not in rv.data
assert <Hello> in rv.data
assert <strong>HTML</strong> allowed here in n rv.data
Here we checkthat HTML is allowed in the text but not in the title, which isthe in-
tendedbehavior.
Runningthatshouldnowgiveusthreepassingtests:
$ python flaskr_tests.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.332s
OK
Formore complex testswith headersandstatuscodes,checkouttheMiniTwitExam-
plefromthesourceswhichcontainsalargertestsuite.
7.6 Other Testing Tricks
Besidesusingthetestclientasshownabove,thereisalsothetest_request_context()
methodthat can be usedin combination with the with statementtoactivate arequest
context temporarily. With thisyou can accessthe request, gand sessionobjectslike
inviewfunctions. Here isafullexample that demonstratesthisapproach:
app = flask.Flask(__name__)
with app.test_request_context(/?name=Peter):
assert flask.request.path == /
assert flask.request.args[name] == Peter
46
37
All the otherobjectsthatare contextboundcan beusedinthe sameway.
If you want to test your application with different configurationsandthere does not
seem to be a good way to do that, consider switching to application factories (see
ApplicationFactories).
Note however that if you are using a test request context, the before_request()
functions are not automatically called same for after_request() functions. How-
ever teardown_request()functionsareindeedexecutedwhenthe testrequestcontext
leavesthe withblock. If you do want the before_request() functionstobe called as
well,youneedtocall preprocess_request()yourself:
app = flask.Flask(__name__)
with app.test_request_context(/?name=Peter):
app.preprocess_request()
...
Thiscan be necessary toopen database connections or something similar depending
onhowyour applicationwasdesigned.
If you want to call the after_request() functions you need to call into
process_response()whichhoweverrequiresthat you passitaresponse object:
app = flask.Flask(__name__)
with app.test_request_context(/?name=Peter):
resp = Response(...)
resp = app.process_response(resp)
...
Thisin generalislessuseful because atthat point you candirectly startusing the test
client.
7.7 Faking Resources and Context
Newinversion0.10.
Averycommon pattern is tostore user authorization information and database con-
nectionsontheapplicationcontextortheflask.gobject. Thegeneralpatternforthisis
toputtheobject on thereonfirstusage andthentoremoveitonateardown. Imagine
forinstance thiscode toget the currentuser:
def get_user():
user = getattr(g, , user, , None)
if user is None:
user = fetch_current_user_from_database()
g.user = user
return user
47
34
For a test it would be nice to override this user from the outside without hav-
ing to change some code. This can trivially be accomplished with hooking the
flask.appcontext_pushedsignal:
from contextlib import contextmanager
from flask import appcontext_pushed
@contextmanager
def user_set(app, user):
def handler(sender, **kwargs):
g.user = user
with appcontext_pushed.connected_to(handler, app):
yield
Andthentouse 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)
7.8 Keeping the Context Around
Newinversion0.4.
Sometimes it is helpful to trigger a regular request but still keep the context around
for a little longer so that additional introspection can happen. With Flask 0.4 this is
possible byusingthetest_client()withawithblock:
app = flask.Flask(__name__)
with app.test_client() as c:
rv = c.get(/?tequila=42)
assert request.args[tequila] == 42
If you were tousejust the test_client()withoutthewithblock,the assert wouldfail
with an error because request is nolonger available (because you are trying to use it
outsideoftheactual request).
48
21
7.9 Accessing and Modifying Sessions
Newinversion0.8.
Sometimesit canbe veryhelpful to accessormodifythe sessionsfrom the testclient.
Generally there are two ways for this. If you just want to ensure that a session has
certain keys set to certain values you can just keep the context around and access
flask.session:
with app.test_client() as c:
rv = c.get(/)
assert flask.session[foo] == 42
Thishoweverdoesnotmake itpossible toalsomodifythesessionor toaccesstheses-
sionbeforearequestwasfired. StartingwithFlask0.8weprovide asocalled“session
transaction” which simulatesthe appropriate callstoopen a session in the context of
the testclientandtomodifyit. Attheendofthe transactionthe sessionisstored. This
worksindependentlyofthe session backendused:
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 flask.session
proxy. Theobjecthoweveritselfwill provide the sameinterface.
49
Documents you may be interested
Documents you may be interested