Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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