Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 242 lines (176 sloc) 8.559 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()
6dd92ae @mitsuhiko Beefed up the tutorial
authored
44 self.app = flaskr.app.test_client()
45 flaskr.init_db()
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
46
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
47 def tearDown(self):
48 os.close(self.db_fd)
d17b6d7 @mitsuhiko Fixed a refacotring error in the docs. This fixes #100
authored
49 os.unlink(flaskr.app.config['DATABASE'])
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
50
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
51 if __name__ == '__main__':
52 unittest.main()
53
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
54 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
55 client and initializes a new database. This function is called before
56 each individual test function is run. To delete the database after the
57 test, we close the file and remove it from the filesystem in the
58 :meth:`~unittest.TestCase.tearDown` method.
59
60 This test client will give us a simple interface to the application. We can
61 trigger test requests to the application, and the client will also keep track
62 of cookies for us.
6dd92ae @mitsuhiko Beefed up the tutorial
authored
63
a7ff9db Proofreading the documentation
Chris Edgemon authored
64 Because SQLite3 is filesystem-based we can easily use the tempfile module
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
65 to create a temporary database and initialize it. The
66 :func:`~tempfile.mkstemp` function does two things for us: it returns a
67 low-level file handle and a random file name, the latter we use as
68 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
69 the :func:`os.close` function to close the file.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
70
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
71 If we now run the test suite, we should see the following output::
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
72
6dd92ae @mitsuhiko Beefed up the tutorial
authored
73 $ python flaskr_tests.py
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
74
75 ----------------------------------------------------------------------
76 Ran 0 tests in 0.000s
f014ce2 @florentx Fix a doc oversight, and revert 5876a8fd.
florentx authored
77
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
78 OK
79
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
80 Even though it did not run any actual tests, we already know that our flaskr
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
81 application is syntactically valid, otherwise the import would have died
82 with an exception.
83
84 The First Test
85 --------------
86
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
87 Now it's time to start testing the functionality of the application.
88 Let's check that the application shows "No entries here so far" if we
89 access the root of the application (``/``). To do this, we add a new
90 test method to our class, like this::
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
91
6dd92ae @mitsuhiko Beefed up the tutorial
authored
92 class FlaskrTestCase(unittest.TestCase):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
93
94 def setUp(self):
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
95 self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
6dd92ae @mitsuhiko Beefed up the tutorial
authored
96 self.app = flaskr.app.test_client()
97 flaskr.init_db()
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
98
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
99 def tearDown(self):
100 os.close(self.db_fd)
101 os.unlink(flaskr.DATABASE)
102
6dd92ae @mitsuhiko Beefed up the tutorial
authored
103 def test_empty_db(self):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
104 rv = self.app.get('/')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
105 assert 'No entries here so far' in rv.data
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
106
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
107 Notice that our test functions begin with the word `test`; this allows
108 :mod:`unittest` to automatically identify the method as a test to run.
109
110 By using `self.app.get` we can send an HTTP `GET` request to the application with
111 the given path. The return value will be a :class:`~flask.Flask.response_class` object.
112 We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect
113 the return value (as string) from the application. In this case, we ensure that
114 ``'No entries here so far'`` is part of the output.
6dd92ae @mitsuhiko Beefed up the tutorial
authored
115
116 Run it again and you should see one passing test::
117
118 $ python flaskr_tests.py
119 .
120 ----------------------------------------------------------------------
121 Ran 1 test in 0.034s
122
123 OK
124
125 Logging In and Out
126 ------------------
127
128 The majority of the functionality of our application is only available for
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
129 the administrative user, so we need a way to log our test client in and out
130 of the application. To do this, we fire some requests to the login and logout
131 pages with the required form data (username and password). And because the
132 login and logout pages redirect, we tell the client to `follow_redirects`.
6dd92ae @mitsuhiko Beefed up the tutorial
authored
133
a7ff9db Proofreading the documentation
Chris Edgemon authored
134 Add the following two methods to your `FlaskrTestCase` class::
6dd92ae @mitsuhiko Beefed up the tutorial
authored
135
136 def login(self, username, password):
137 return self.app.post('/login', data=dict(
138 username=username,
139 password=password
140 ), follow_redirects=True)
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
141
6dd92ae @mitsuhiko Beefed up the tutorial
authored
142 def logout(self):
143 return self.app.get('/logout', follow_redirects=True)
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
144
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
145 Now we can easily test that logging in and out works and that it fails with
a7ff9db Proofreading the documentation
Chris Edgemon authored
146 invalid credentials. Add this new test to the class::
6dd92ae @mitsuhiko Beefed up the tutorial
authored
147
148 def test_login_logout(self):
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
149 rv = self.login('admin', 'default')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
150 assert 'You were logged in' in rv.data
151 rv = self.logout()
152 assert 'You were logged out' in rv.data
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
153 rv = self.login('adminx', 'default')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
154 assert 'Invalid username' in rv.data
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
155 rv = self.login('admin', 'defaultx')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
156 assert 'Invalid password' in rv.data
157
158 Test Adding Messages
159 --------------------
160
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
161 We should also test that adding messages works. Add a new test method
6dd92ae @mitsuhiko Beefed up the tutorial
authored
162 like this::
163
164 def test_messages(self):
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
165 self.login('admin', 'default')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
166 rv = self.app.post('/add', data=dict(
167 title='<Hello>',
168 text='<strong>HTML</strong> allowed here'
169 ), follow_redirects=True)
170 assert 'No entries here so far' not in rv.data
94a7312 @dhaaker Add missing semicolon to test
dhaaker authored
171 assert '&lt;Hello&gt;' in rv.data
6dd92ae @mitsuhiko Beefed up the tutorial
authored
172 assert '<strong>HTML</strong> allowed here' in rv.data
173
a7ff9db Proofreading the documentation
Chris Edgemon authored
174 Here we check that HTML is allowed in the text but not in the title,
6dd92ae @mitsuhiko Beefed up the tutorial
authored
175 which is the intended behavior.
176
177 Running that should now give us three passing tests::
178
a224fec @birkenfeld More typo fixes.
birkenfeld authored
179 $ python flaskr_tests.py
6dd92ae @mitsuhiko Beefed up the tutorial
authored
180 ...
181 ----------------------------------------------------------------------
182 Ran 3 tests in 0.332s
a224fec @birkenfeld More typo fixes.
birkenfeld authored
183
6dd92ae @mitsuhiko Beefed up the tutorial
authored
184 OK
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
185
6dd92ae @mitsuhiko Beefed up the tutorial
authored
186 For more complex tests with headers and status codes, check out the
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
187 `MiniTwit Example`_ from the sources which contains a larger test
6dd92ae @mitsuhiko Beefed up the tutorial
authored
188 suite.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
189
190
191 .. _MiniTwit Example:
192 http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
8d49440 @mitsuhiko Added example for context bound objects to the testing docs. This fixes...
authored
193
194
195 Other Testing Tricks
196 --------------------
197
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
198 Besides using the test client as shown above, there is also the
199 :meth:`~flask.Flask.test_request_context` method that can be used
200 in combination with the `with` statement to activate a request context
201 temporarily. With this you can access the :class:`~flask.request`,
8d49440 @mitsuhiko Added example for context bound objects to the testing docs. This fixes...
authored
202 :class:`~flask.g` and :class:`~flask.session` objects like in view
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
203 functions. Here is a full example that demonstrates this approach::
8d49440 @mitsuhiko Added example for context bound objects to the testing docs. This fixes...
authored
204
205 app = flask.Flask(__name__)
206
207 with app.test_request_context('/?name=Peter'):
208 assert flask.request.path == '/'
209 assert flask.request.args['name'] == 'Peter'
210
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
211 All the other objects that are context bound can be used in the same
212 way.
dfecc86 @mitsuhiko Ported examples over to new config. documented upgrading
authored
213
214 If you want to test your application with different configurations and
215 there does not seem to be a good way to do that, consider switching to
216 application factories (see :ref:`app-factories`).
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now ac...
authored
217
218
219 Keeping the Context Around
220 --------------------------
221
222 .. versionadded:: 0.4
223
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
224 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 ac...
authored
225 context around for a little longer so that additional introspection can
226 happen. With Flask 0.4 this is possible by using the
227 :meth:`~flask.Flask.test_client` with a `with` block::
228
229 app = flask.Flask(__name__)
230
231 with app.test_client() as c:
20c2e53 @mitsuhiko Alcoholified placeholders
authored
232 rv = c.get('/?tequila=42')
233 assert request.args['tequila'] == '42'
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now ac...
authored
234
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
235 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 ac...
authored
236 the `with` block, the `assert` would fail with an error because `request`
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
237 is no longer available (because you are trying to use it outside of the actual request).
238 However, keep in mind that any :meth:`~flask.Flask.after_request` functions
239 are already called at this point so your database connection and
bc00fd1 @mitsuhiko Added support for deferred context cleanup. test_client users can now ac...
authored
240 everything involved is probably already closed down.
f58c989 @swanson fixing some wording issues on the testing page
swanson authored
241
Something went wrong with that request. Please try again.