Contents
The TurboGears 1.0 testing system has been a limiting factor in moving TurboGears forward [1]. Regardless of whether the “future” involves CherryPy 3.0, Pylons, or alien technology (read: Google AppEngine), the dependencies of testutil.create_request, etc. on the internals of CherryPy 2.0 restricts TurboGears from using the features of these newer systems.
The core TurboGears developers identified this problem at least as early as May, 2007 [2], and have agreed that a framework agnostic testing tool would provide a solution that is in line with TurboGears’ philosophy. Ian Bicking’s WebTest has been selected as the best tool for this purpose, and is being introduced in the TG 1.1 series.
In order to support this move, several functions in testutil have been deprecated in favor of the the new methodology. You can continue to run your tests unchanged for now, but you will receive DeprecationWarnings. The deprecated features will be removed in the next version of TurboGears, 1.5.
Note
Earlier versions of this document emphasized the use of mount & make_app over the use of TGTest. In response to feedback, this has been reversed in the current version. TGTest now receives primary attention and mount & make_app are reserved for discussion in the “Other Testing Approaches” of the main Testing document. This is purely a matter of emphasis; the approach detailed in the old doc is still documented & supported.
testutil.TGTest is a unittest.TestCase-style class that makes it easy to setup your TurboGears tests. Simply subclass it and assign your RootController to the root attribute of your subclass. Your test methods will have access to a WebTest.TestApp instance as self.app. We’ll describe WebTest in more detail below, but for now let’s look at a simple test:
class TestIt(turbogears.testutil.TGTest):
root = myproject.controllers.Root
def test_index(self):
response = self.app.get('/index')
assert 'Index this' in response
The main thing to note for people upgrading from TG 1.0 is that instead of mounting our application by assigning our root controller to cherrypy.root, we assign it to a root attribute of our test class.
TGTest will do the work of starting the server, mounting your application, creating a WebTest.TestApp instance and assigning it to self.app to make it available to you in your testing. If you need finer-grained control of how your tests are set up, you can find it covered in the “Other Testing Approaches” of the main Testing document.
So let’s get back to that code segment. Once we’ve set up our testutil.TGTest subclass, we’re ready to start using the features of WebTest. As covered above, the self.app attribute is a WebTest.TestApp instance, set up & ready to test the application. This instance has a method for every HTTP verb, each of which returns a response object. You need to use this response in your tests instead of cherrypy.response [3]. For example, the TG 1.0 test:
cherrypy.root = MyRoot()
testutil.create_request('/mysite')
assert 'is groovy' in cherrypy.response.body[0]
becomes this in TG 1.1:
response = self.app.get('/mysite')
assert 'is groovy' in response
In addition to WebTest’s normal attributes, this response also contains your controller’s return value under the raw attribute. This features allows app.get() to be the replacement not only for testutil.create_request, but also testutil.call, and testutil.call_with_request as well.
TurboGears 1.0:
d = testutil.call(cherrypy.root.foo)
assert d["title"] == "Foobar"
Becomes this in >= 1.1:
d = self.app.get("/foo")
assert d.raw['title'] == "Foobar"
Finally, WebTest will automatically check the status code for your application. By default, any status code other than 2XX or 3XX will be a test failure. You can override this behavior by specifying the status code that you expect on the status parameter. Pass '*' to accept any status.
testutil.set_identity_user and testutil.attach_identity have been deprecated. Instead of manipulating the identity system behind the scenes, it’s recommended that your tests log in to your application the same way they would in a live environment:
def test_secure_access(self):
response = self.app.get('/secured?'
'user_name=frodo&password=secret&login=Login')
assert 'Logged in' in response
session_id = response.headers['Set-Cookie']
response = self.app.get('/logout', headers={'Cookie': session_id })
assert response.body == 'Logged out' in response
If you absolutely need to fake the login process, you should be able to do so by passing REMOTE_USER into the extra_environ dictionary:
self.app.get('/secret', extra_environ=dict(REMOTE_USER='bob'))
But that’s untested, so your mileage may vary.
CherryPy’s response object has a simple_cookie attribute, which is a SimpleCookie instance. In WebTest’s response, there is a cookies_set attribute, which is a dictionary. Should you need to access the raw cookie string, it’s available as response.headers['Set-Cookie'].
What if you want to do more than just a HTTP “get”? This wasn’t possible using create_request, but is simple as pie using WebTest. Let’s check out a simple form example first:
response = self.app.get('/mysite')
response.form['foo'] = 1
response.form.submit()
Put and delete are similarly easy:
self.app.put('/mysite/1', params)
self.app.delete('/mysite/1')
This has been a quick, but complete, run-through of all of the new & deprecated features of the testutil module. The main Testing document covers this same information, with more background information for people new to testing with TurboGears and without the discussion of deprecated functionality. The docs for WebTest are also good, so be sure to read those if you have any questions.
The items below are expected to only impact the internal TurboGears tests; they shouldn’t impact tests of applications based on TurboGears. They’re only included here for the sake of completeness.
[1] | See Testing form submittal (2007-12-29) ANN: TurboGears 1.0.4.2 Released (2008-01-21) and 1.0 and 1.1 testing (2008-03-18) |
[2] | See http://www.cherrypy.org/wiki/TGIRC20070526, http://groups.google.com/group/turbogears/browse_thread/thread/6eca2f550b13a8a5 |
[3] | (1, 2) If you try to use cherrypy.request after using WebTest to hit your page, you’ll get an error:: AttributeError: cherrypy.request has no properties outside of an HTTP request. |