Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 304 lines (223 sloc) 11.07 kB
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
1 .. _testing:
2
3 Testing Flask Applications
4 ==========================
5
6 **Something that is untested is broken.**
7
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
8 The origin of this quote is unknown and while it is not entirely correct, it is also
9 not far from the truth. Untested applications make it hard to
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
10 improve existing code and developers of untested applications tend to
505c530 @adamzap Minor testing documentation fixes (grammar, etc)
adamzap authored
11 become pretty paranoid. If an application has automated tests, you can
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
12 safely make changes and instantly know if anything breaks.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
13
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
14 Flask provides a way to test your application by exposing the Werkzeug
15 test :class:`~werkzeug.test.Client` and handling the context locals for you.
16 You can then use that with your favourite testing solution. In this documentation
17 we will use the :mod:`unittest` package that comes pre-installed with Python.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
18
19 The Application
20 ---------------
21
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
22 First, we need an application to test; we will use the application from
23 the :ref:`tutorial`. If you don't have that application yet, get the
24 sources from `the examples`_.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
25
6dd92ae @mitsuhiko Beefed up the tutorial
authored
26 .. _the examples:
27 http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
28
29 The Testing Skeleton
30 --------------------
31
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
32 In order to test the application, we add a second module
33 (`flaskr_tests.py`) and create a unittest skeleton there::
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
34
f6b9efc @mitsuhiko Added missing os import.
authored
35 import os
6dd92ae @mitsuhiko Beefed up the tutorial
authored
36 import flaskr
f6b9efc @mitsuhiko Added missing os import.
authored
37 import unittest
6dd92ae @mitsuhiko Beefed up the tutorial
authored
38 import tempfile
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
39
6dd92ae @mitsuhiko Beefed up the tutorial
authored
40 class FlaskrTestCase(unittest.TestCase):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
41
42 def setUp(self):
0566abc @mitsuhiko Fixed testing example
authored
43 self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
7e55b50 @mitsuhiko Mention the TESTING flag in the docs
authored
44 flaskr.app.config['TESTING'] = True
6dd92ae @mitsuhiko Beefed up the tutorial
authored
45 self.app = flaskr.app.test_client()
46 flaskr.init_db()
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
47
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
48 def tearDown(self):
49 os.close(self.db_fd)
d17b6d7 @mitsuhiko Fixed a refacotring error in the docs. This fixes #100
authored
50 os.unlink(flaskr.app.config['DATABASE'])
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
51
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
52 if __name__ == '__main__':
53 unittest.main()
54
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
55 The code in the :meth:`~unittest.TestCase.setUp` method creates a new test
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
56 client and initializes a new database. This function is called before
57 each individual test function is run. To delete the database after the
58 test, we close the file and remove it from the filesystem in the
7e55b50 @mitsuhiko Mention the TESTING flag in the docs
authored
59 :meth:`~unittest.TestCase.tearDown` method. Additionally during setup the
60 ``TESTING`` config flag is activated. What it does is disabling the error
61 catching during request handling so that you get better error reports when
62 performing test requests against the application.
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
63
64 This test client will give us a simple interface to the application. We can
65 trigger test requests to the application, and the client will also keep track
66 of cookies for us.
6dd92ae @mitsuhiko Beefed up the tutorial
authored
67
a7ff9db Proofreading the documentation
Chris Edgemon authored
68 Because SQLite3 is filesystem-based we can easily use the tempfile module
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
69 to create a temporary database and initialize it. The
70 :func:`~tempfile.mkstemp` function does two things for us: it returns a
71 low-level file handle and a random file name, the latter we use as
72 database name. We just have to keep the `db_fd` around so that we can use
f014ce2 @florentx Fix a doc oversight, and revert 5876a8fd.
florentx authored
73 the :func:`os.close` function to close the file.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
74
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
75 If we now run the test suite, we should see the following output::
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
76
6dd92ae @mitsuhiko Beefed up the tutorial
authored
77 $ python flaskr_tests.py
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
78
79 ----------------------------------------------------------------------
80 Ran 0 tests in 0.000s
f014ce2 @florentx Fix a doc oversight, and revert 5876a8fd.
florentx authored
81
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
82 OK
83
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
84 Even though it did not run any actual tests, we already know that our flaskr
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
85 application is syntactically valid, otherwise the import would have died
86 with an exception.
87
88 The First Test
89 --------------
90
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
91 Now it's time to start testing the functionality of the application.
92 Let's check that the application shows "No entries here so far" if we
93 access the root of the application (``/``). To do this, we add a new
94 test method to our class, like this::
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
95
6dd92ae @mitsuhiko Beefed up the tutorial
authored
96 class FlaskrTestCase(unittest.TestCase):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
97
98 def setUp(self):
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
99 self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
6dd92ae @mitsuhiko Beefed up the tutorial
authored
100 self.app = flaskr.app.test_client()
101 flaskr.init_db()
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
102
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
103 def tearDown(self):
104 os.close(self.db_fd)
a754d28 @adityaathalye Database improperly closed in example code
adityaathalye authored
105 os.unlink(flaskr.app.config['DATABASE'])
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
106
6dd92ae @mitsuhiko Beefed up the tutorial
authored
107 def test_empty_db(self):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
108 rv = self.app.get('/')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
109 assert 'No entries here so far' in rv.data
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
110
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
111 Notice that our test functions begin with the word `test`; this allows
112 :mod:`unittest` to automatically identify the method as a test to run.
113
114 By using `self.app.get` we can send an HTTP `GET` request to the application with
115 the given path. The return value will be a :class:`~flask.Flask.response_class` object.
116 We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect
117 the return value (as string) from the application. In this case, we ensure that
118 ``'No entries here so far'`` is part of the output.
6dd92ae @mitsuhiko Beefed up the tutorial
authored
119
120 Run it again and you should see one passing test::
121
122 $ python flaskr_tests.py
123 .
124 ----------------------------------------------------------------------
125 Ran 1 test in 0.034s
126
127 OK
128
129 Logging In and Out
130 ------------------
131
132 The majority of the functionality of our application is only available for
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
133 the administrative user, so we need a way to log our test client in and out
134 of the application. To do this, we fire some requests to the login and logout
135 pages with the required form data (username and password). And because the
136 login and logout pages redirect, we tell the client to `follow_redirects`.
6dd92ae @mitsuhiko Beefed up the tutorial
authored
137
a7ff9db Proofreading the documentation
Chris Edgemon authored
138 Add the following two methods to your `FlaskrTestCase` class::
6dd92ae @mitsuhiko Beefed up the tutorial
authored
139
140 def login(self, username, password):
141 return self.app.post('/login', data=dict(
142 username=username,
143 password=password
144 ), follow_redirects=True)
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
145
6dd92ae @mitsuhiko Beefed up the tutorial
authored
146 def logout(self):
147 return self.app.get('/logout', follow_redirects=True)
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
148
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
149 Now we can easily test that logging in and out works and that it fails with
a7ff9db Proofreading the documentation
Chris Edgemon authored
150 invalid credentials. Add this new test to the class::
6dd92ae @mitsuhiko Beefed up the tutorial
authored
151
152 def test_login_logout(self):
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
153 rv = self.login('admin', 'default')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
154 assert 'You were logged in' in rv.data
155 rv = self.logout()
156 assert 'You were logged out' in rv.data
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
157 rv = self.login('adminx', 'default')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
158 assert 'Invalid username' in rv.data
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
159 rv = self.login('admin', 'defaultx')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
160 assert 'Invalid password' in rv.data
161
162 Test Adding Messages
163 --------------------
164
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
165 We should also test that adding messages works. Add a new test method
6dd92ae @mitsuhiko Beefed up the tutorial
authored
166 like this::
167
168 def test_messages(self):
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
169 self.login('admin', 'default')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
170 rv = self.app.post('/add', data=dict(
171 title='<Hello>',
172 text='<strong>HTML</strong> allowed here'
173 ), follow_redirects=True)
174 assert 'No entries here so far' not in rv.data
94a7312 @dhaaker Add missing semicolon to test
dhaaker authored
175 assert '&lt;Hello&gt;' in rv.data
6dd92ae @mitsuhiko Beefed up the tutorial
authored
176 assert '<strong>HTML</strong> allowed here' in rv.data
177
a7ff9db Proofreading the documentation
Chris Edgemon authored
178 Here we check that HTML is allowed in the text but not in the title,
6dd92ae @mitsuhiko Beefed up the tutorial
authored
179 which is the intended behavior.
180
181 Running that should now give us three passing tests::
182
a224fec @birkenfeld More typo fixes.
birkenfeld authored
183 $ python flaskr_tests.py
6dd92ae @mitsuhiko Beefed up the tutorial
authored
184 ...
185 ----------------------------------------------------------------------
186 Ran 3 tests in 0.332s
a224fec @birkenfeld More typo fixes.
birkenfeld authored
187
6dd92ae @mitsuhiko Beefed up the tutorial
authored
188 OK
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
189
6dd92ae @mitsuhiko Beefed up the tutorial
authored
190 For more complex tests with headers and status codes, check out the
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
191 `MiniTwit Example`_ from the sources which contains a larger test
6dd92ae @mitsuhiko Beefed up the tutorial
authored
192 suite.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
193
194
195 .. _MiniTwit Example:
196 http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
8d49440 @mitsuhiko Added example for context bound objects to the testing docs. This fi…
authored
197
198
199 Other Testing Tricks
200 --------------------
201
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
202 Besides using the test client as shown above, there is also the
203 :meth:`~flask.Flask.test_request_context` method that can be used
204 in combination with the `with` statement to activate a request context
205 temporarily. With this you can access the :class:`~flask.request`,
8d49440 @mitsuhiko Added example for context bound objects to the testing docs. This fi…
authored
206 :class:`~flask.g` and :class:`~flask.session` objects like in view
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
207 functions. Here is a full example that demonstrates this approach::
8d49440 @mitsuhiko Added example for context bound objects to the testing docs. This fi…
authored
208
209 app = flask.Flask(__name__)
210
211 with app.test_request_context('/?name=Peter'):
212 assert flask.request.path == '/'
213 assert flask.request.args['name'] == 'Peter'
214
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
215 All the other objects that are context bound can be used in the same
216 way.
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
217
218 If you want to test your application with different configurations and
219 there does not seem to be a good way to do that, consider switching to
220 application factories (see :ref:`app-factories`).
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now…
authored
221
7e55b50 @mitsuhiko Mention the TESTING flag in the docs
authored
222 Note however that if you are using a test request context, the
223 :meth:`~flask.Flask.before_request` functions are not automatically called
c8663e8 @mitsuhiko Fixed a typo in the docs.
authored
224 same for :meth:`~flask.Flask.after_request` functions. However
7e55b50 @mitsuhiko Mention the TESTING flag in the docs
authored
225 :meth:`~flask.Flask.teardown_request` functions are indeed executed when
226 the test request context leaves the `with` block. If you do want the
227 :meth:`~flask.Flask.before_request` functions to be called as well, you
228 need to call :meth:`~flask.Flask.preprocess_request` yourself::
229
230 app = flask.Flask(__name__)
231
232 with app.test_request_context('/?name=Peter'):
233 app.preprocess_request()
234 ...
235
236 This can be necessary to open database connections or something similar
237 depending on how your application was designed.
238
99c2def @mitsuhiko Added an example of how to postprocess requests in the testing docs.
authored
239 If you want to call the :meth:`~flask.Flask.after_request` functions you
240 need to call into :meth:`~flask.Flask.process_response` which however
241 requires that you pass it a response object::
242
243 app = flask.Flask(__name__)
244
245 with app.test_request_context('/?name=Peter'):
246 resp = Response('...')
247 resp = app.process_response(resp)
248 ...
249
250 This in general is less useful because at that point you can directly
251 start using the test client.
252
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now…
authored
253
254 Keeping the Context Around
255 --------------------------
256
257 .. versionadded:: 0.4
258
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
259 Sometimes it is helpful to trigger a regular request but still keep the
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now…
authored
260 context around for a little longer so that additional introspection can
261 happen. With Flask 0.4 this is possible by using the
262 :meth:`~flask.Flask.test_client` with a `with` block::
263
264 app = flask.Flask(__name__)
265
266 with app.test_client() as c:
20c2e53 @mitsuhiko Alcoholified placeholders
authored
267 rv = c.get('/?tequila=42')
268 assert request.args['tequila'] == '42'
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now…
authored
269
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
270 If you were to use just the :meth:`~flask.Flask.test_client` without
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now…
authored
271 the `with` block, the `assert` would fail with an error because `request`
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
272 is no longer available (because you are trying to use it outside of the actual request).
1ea3d4e @mitsuhiko Updated documentation regarding the session transactions
authored
273
274 Accessing and Modifying Sessions
275 --------------------------------
276
277 .. versionadded:: 0.8
278
279 Sometimes it can be very helpful to access or modify the sessions from the
a43f73c @DasIch s/Ify ou/If you/ typo
DasIch authored
280 test client. Generally there are two ways for this. If you just want to
1ea3d4e @mitsuhiko Updated documentation regarding the session transactions
authored
281 ensure that a session has certain keys set to certain values you can just
282 keep the context around and access :data:`flask.session`::
283
284 with app.test_client() as c:
285 rv = c.get('/')
286 assert flask.session['foo'] == 42
287
288 This however does not make it possible to also modify the session or to
289 access the session before a request was fired. Starting with Flask 0.8 we
290 provide a so called “session transaction” which simulates the appropriate
291 calls to open a session in the context of the test client and to modify
292 it. At the end of the transaction the session is stored. This works
293 independently of the session backend used::
294
295 with app.test_client() as c:
296 with c.session_transaction() as sess:
297 sess['a_key'] = 'a value'
298
299 # once this is reached the session was stored
300
301 Note that in this case you have to use the ``sess`` object instead of the
302 :data:`flask.session` proxy. The object however itself will provide the
303 same interface.
Something went wrong with that request. Please try again.