Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 187 lines (139 sloc) 6.575 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
38 import unittest
6dd92ae @mitsuhiko Beefed up the tutorial
authored
39 import flaskr
40 import tempfile
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
41
6dd92ae @mitsuhiko Beefed up the tutorial
authored
42 class FlaskrTestCase(unittest.TestCase):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
43
44 def setUp(self):
6dd92ae @mitsuhiko Beefed up the tutorial
authored
45 self.db = tempfile.NamedTemporaryFile()
46 self.app = flaskr.app.test_client()
47 flaskr.DATABASE = self.db.name
48 flaskr.init_db()
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
49
50 if __name__ == '__main__':
51 unittest.main()
52
a7ff9db Proofreading the documentation
Chris Edgemon authored
53 The code in the `setUp` function creates a new test client and initializes
6dd92ae @mitsuhiko Beefed up the tutorial
authored
54 a new database. That function is called before each individual test function.
a7ff9db Proofreading the documentation
Chris Edgemon authored
55 What the test client does is give us a simple interface to the
6dd92ae @mitsuhiko Beefed up the tutorial
authored
56 application. We can trigger test requests to the application and the
57 client will also keep track of cookies for us.
58
a7ff9db Proofreading the documentation
Chris Edgemon authored
59 Because SQLite3 is filesystem-based we can easily use the tempfile module
6dd92ae @mitsuhiko Beefed up the tutorial
authored
60 to create a temporary database and initialize it. Just make sure that you
61 keep a reference to the :class:`~tempfile.NamedTemporaryFile` around (we
62 store it as `self.db` because of that) so that the garbage collector does
63 not remove that object and with it the database from the filesystem.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
64
65 If we now run that testsuite, we should see the following output::
66
6dd92ae @mitsuhiko Beefed up the tutorial
authored
67 $ python flaskr_tests.py
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
68
69 ----------------------------------------------------------------------
70 Ran 0 tests in 0.000s
71
72 OK
73
6dd92ae @mitsuhiko Beefed up the tutorial
authored
74 Even though it did not run any tests, we already know that our flaskr
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
75 application is syntactically valid, otherwise the import would have died
76 with an exception.
77
78 The First Test
79 --------------
80
6dd92ae @mitsuhiko Beefed up the tutorial
authored
81 Now we can add the first test. Let's check that the application shows
82 "No entries here so far" if we access the root of the application (``/``).
83 For that we modify our created test case class so that it looks like
84 this::
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
85
6dd92ae @mitsuhiko Beefed up the tutorial
authored
86 class FlaskrTestCase(unittest.TestCase):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
87
88 def setUp(self):
6dd92ae @mitsuhiko Beefed up the tutorial
authored
89 self.db = tempfile.NamedTemporaryFile()
90 self.app = flaskr.app.test_client()
91 flaskr.DATABASE = self.db.name
92 flaskr.init_db()
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
93
6dd92ae @mitsuhiko Beefed up the tutorial
authored
94 def test_empty_db(self):
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
95 rv = self.app.get('/')
6dd92ae @mitsuhiko Beefed up the tutorial
authored
96 assert 'No entries here so far' in rv.data
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
97
98 Test functions begin with the word `test`. Every function named like that
99 will be picked up automatically. By using `self.app.get` we can send an
100 HTTP `GET` request to the application with the given path. The return
101 value will be a :class:`~flask.Flask.response_class` object. We can now
102 use the :attr:`~werkzeug.BaseResponse.data` attribute to inspect the
103 return value (as string) from the application. In this case, we ensure
6dd92ae @mitsuhiko Beefed up the tutorial
authored
104 that ``'No entries here so far'`` is part of the output.
105
106 Run it again and you should see one passing test::
107
108 $ python flaskr_tests.py
109 .
110 ----------------------------------------------------------------------
111 Ran 1 test in 0.034s
112
113 OK
114
a7ff9db Proofreading the documentation
Chris Edgemon authored
115 Of course you can submit forms with the test client as well, which we will
6dd92ae @mitsuhiko Beefed up the tutorial
authored
116 use now to log our user in.
117
118 Logging In and Out
119 ------------------
120
121 The majority of the functionality of our application is only available for
a7ff9db Proofreading the documentation
Chris Edgemon authored
122 the administration user. So we need a way to log our test client in to the
6dd92ae @mitsuhiko Beefed up the tutorial
authored
123 application and out of it again. For that we fire some requests to the
124 login and logout pages with the required form data (username and
125 password). Because the login and logout pages redirect, we tell the
126 client to `follow_redirects`.
127
a7ff9db Proofreading the documentation
Chris Edgemon authored
128 Add the following two methods to your `FlaskrTestCase` class::
6dd92ae @mitsuhiko Beefed up the tutorial
authored
129
130 def login(self, username, password):
131 return self.app.post('/login', data=dict(
132 username=username,
133 password=password
134 ), follow_redirects=True)
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
135
6dd92ae @mitsuhiko Beefed up the tutorial
authored
136 def logout(self):
137 return self.app.get('/logout', follow_redirects=True)
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
138
6dd92ae @mitsuhiko Beefed up the tutorial
authored
139 Now we can easily test if logging in and out works and that it fails with
a7ff9db Proofreading the documentation
Chris Edgemon authored
140 invalid credentials. Add this new test to the class::
6dd92ae @mitsuhiko Beefed up the tutorial
authored
141
142 def test_login_logout(self):
143 rv = self.login(flaskr.USERNAME, flaskr.PASSWORD)
144 assert 'You were logged in' in rv.data
145 rv = self.logout()
146 assert 'You were logged out' in rv.data
147 rv = self.login(flaskr.USERNAME + 'x', flaskr.PASSWORD)
148 assert 'Invalid username' in rv.data
149 rv = self.login(flaskr.USERNAME, flaskr.PASSWORD + 'x')
150 assert 'Invalid password' in rv.data
151
152 Test Adding Messages
153 --------------------
154
155 Now we can also test that adding messages works. Add a new test method
156 like this::
157
158 def test_messages(self):
159 self.login(flaskr.USERNAME, flaskr.PASSWORD)
160 rv = self.app.post('/add', data=dict(
161 title='<Hello>',
162 text='<strong>HTML</strong> allowed here'
163 ), follow_redirects=True)
164 assert 'No entries here so far' not in rv.data
165 assert '&lt;Hello&gt' in rv.data
166 assert '<strong>HTML</strong> allowed here' in rv.data
167
a7ff9db Proofreading the documentation
Chris Edgemon authored
168 Here we check that HTML is allowed in the text but not in the title,
6dd92ae @mitsuhiko Beefed up the tutorial
authored
169 which is the intended behavior.
170
171 Running that should now give us three passing tests::
172
173 $ python flaskr_tests.py
174 ...
175 ----------------------------------------------------------------------
176 Ran 3 tests in 0.332s
177
178 OK
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
179
6dd92ae @mitsuhiko Beefed up the tutorial
authored
180 For more complex tests with headers and status codes, check out the
181 `MiniTwit Example`_ from the sources. That one contains a larger test
182 suite.
2f5a4f8 @mitsuhiko Doc updates and typo fixes
authored
183
184
185 .. _MiniTwit Example:
186 http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
Something went wrong with that request. Please try again.