Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 198 lines (148 sloc) 6.913 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
a7ff9db Proofreading the documentation
Chris Edgemon authored
11 become pretty paranoid. If an application however has automated tests, you
12 can 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
63 requests to the application and the client will also keep track of cookies
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
73 If we now run that testsuite, we should see the following output::
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):
3053fcd @mitsuhiko Make the example tests pass on Windows.
authored
97 self.db_fd, flaskr.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
a7ff9db Proofreading the documentation
Chris Edgemon authored
133 the administration 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):
154 rv = self.login(flaskr.USERNAME, flaskr.PASSWORD)
155 assert 'You were logged in' in rv.data
156 rv = self.logout()
157 assert 'You were logged out' in rv.data
158 rv = self.login(flaskr.USERNAME + 'x', flaskr.PASSWORD)
159 assert 'Invalid username' in rv.data
160 rv = self.login(flaskr.USERNAME, flaskr.PASSWORD + 'x')
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):
170 self.login(flaskr.USERNAME, flaskr.PASSWORD)
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
184 $ python flaskr_tests.py
185 ...
186 ----------------------------------------------------------------------
187 Ran 3 tests in 0.332s
188
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/
Something went wrong with that request. Please try again.