Index: setup.py
===================================================================
--- setup.py	(revision 4618)
+++ setup.py	(working copy)
@@ -24,6 +24,7 @@
     "TurboCheetah >= 1.0",
     "TurboJson >= 1.1.2",
     "tgMochiKit >= 0.1alpha",
+    "WebTest",
 ]
 
 # for when we get rid of Kid & SQLObject dependency
Index: turbogears/identity/tests/test_identity.py
===================================================================
--- turbogears/identity/tests/test_identity.py	(revision 4618)
+++ turbogears/identity/tests/test_identity.py	(working copy)
@@ -55,7 +55,7 @@
 
     @expose()
     def index(self):
-        pass
+        return dict()
 
     @expose()
     def identity_failed(self, **kw):
@@ -69,6 +69,7 @@
     @expose()
     @identity.require(in_group('peon'))
     def in_peon_group(self):
+        assert("samIam", cherrypy.serving.request.identity.user_name)
         return 'in_peon_group'
 
     @expose()
@@ -136,9 +137,17 @@
             return 'wrong params: %s\nexpected unicode objects' \
                 ' for all strings' % cherrypy.request.params
 
+    @expose()
+    def is_anonymous(self):
+        assert cherrypy.serving.request.identity.user_name == None
+        assert cherrypy.serving.request.identity.anonymous
+        assert identity.current.anonymous
+        return 'is_anonymous'
 
-class TestIdentity(unittest.TestCase):
 
+
+class TestIdentity(testutil.TGWebTest):
+
     def setUp(self):
         # identity requires visit and a failure_url
         test_config = {'global': {
@@ -152,11 +161,11 @@
         config.configure_loggers(test_config)
         config.update(test_config['global'])
         cherrypy.root = IdentityRoot()
-        startup.startTurboGears()
+        testutil.TGWebTest.setUp(self)
         self.init_model()
 
     def tearDown(self):
-        startup.stopTurboGears()
+        testutil.TGWebTest.tearDown(self)
         config.update(self._original_config)
 
     def init_model(self):
@@ -181,9 +190,8 @@
 
     def test_user_password_parameters(self):
         """Controller can receive user_name and password parameters."""
-        testutil.create_request('/new_user_setup?user_name=test&password=pw')
-        firstline = cherrypy.response.body[0]
-        assert 'test pw' in firstline, firstline
+        response = self.app.get('/new_user_setup?user_name=test&password=pw')
+        assert 'test pw' in response
 
     def test_user_exists(self):
         u = TG_User.by_user_name('samIam')
@@ -205,7 +213,7 @@
         config.update({'identity.soprovider.encryption_algorithm': None})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'
@@ -219,7 +227,7 @@
         config.update({'identity.soprovider.encryption_algorithm': 'sha1'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = 'password'
@@ -234,7 +242,7 @@
         config.update({'identity.soprovider.encryption_algorithm': 'sha1'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'
@@ -248,7 +256,7 @@
         config.update({'identity.soprovider.encryption_algorithm': 'md5'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = 'password'
@@ -263,7 +271,7 @@
         config.update({'identity.soprovider.encryption_algorithm': 'md5'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'
@@ -280,7 +288,7 @@
         config.update({'identity.soprovider.encryption_algorithm': 'md5'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'.encode('UTF-8')
@@ -295,7 +303,7 @@
         config.update({'identity.soprovider.encryption_algorithm':'sha1'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.set_password_raw('password')
@@ -308,7 +316,7 @@
         config.update({'identity.soprovider.encryption_algorithm':'sha1'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.set_password_raw(u'garçon')
@@ -324,7 +332,7 @@
                 'identity.tests.test_identity.mycustomencrypt'})
         # force new config values to load
         startup.startTurboGears()
-        testutil.create_request('/')
+        self.app.get('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = 'password'
@@ -335,33 +343,29 @@
 
     def test_anonymous_browsing(self):
         """Test if we can show up anonymously."""
-        testutil.create_request('/')
-        assert identity.current.anonymous
+        response = self.app.get('/is_anonymous')
+        assert 'is_anonymous' in response
 
     def test_deny_anonymous(self):
         """Test that we have secured an url from anonymous users."""
-        testutil.create_request('/logged_in_only')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        response = self.app.get('/logged_in_only')
+        assert 'identity_failed_answer' in response
 
     def test_deny_anonymous_viewable(self):
         """Test that a logged in user can see an resource blocked
         from anonymous users."""
-        testutil.create_request('/logged_in_only?'
+        response = self.app.get('/logged_in_only?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'logged_in_only' in firstline, firstline
+        assert 'logged_in_only' in response
 
     def test_logout(self):
         """Test that logout works and session is gets invalid afterwards."""
-        testutil.create_request('/in_peon_group?'
+        response = self.app.get('/in_peon_group?'
             'user_name=samIam&password=secret&login=Login')
-        self.assertEquals("samIam", cherrypy.serving.request.identity.user_name)
-        session_id = re.match("Set-Cookie: (.*?); Path.*",
-            str(cherrypy.response.simple_cookie)).group(1)
-        testutil.create_request('/logout', headers={'Cookie': session_id })
-        self.assertEquals(None, cherrypy.serving.request.identity.user_name)
-        assert cherrypy.serving.request.identity.anonymous
+        session_id = response.headers['Set-Cookie']
+        response = self.app.get('/logout', headers={'Cookie': session_id })
+        response = self.app.get('/is_anonymous', headers={'Cookie': session_id})
+        assert response.body == 'is_anonymous'
 
     def test_logout_with_set_identity(self):
         """Test that logout works even when there is no visit_key
@@ -522,18 +526,12 @@
     def setUp(self):
         self._identity_on = config.get('identity.on', False)
         config.update({'identity.on': False})
-        try:
-            self._provider = cherrypy.request.identityProvider
-        except AttributeError:
-            self._provider= None
-        cherrypy.request.identityProvider = None
         startup.startTurboGears()
         testutil.DBTest.setUp(self)
 
     def tearDown(self):
         testutil.DBTest.tearDown(self)
         startup.stopTurboGears()
-        cherrypy.request.identityProvider = self._provider
         config.update({'identity.on': self._identity_on})
 
     def test_create_user(self):
Index: turbogears/tests/test_form_controllers.py
===================================================================
--- turbogears/tests/test_form_controllers.py	(revision 4618)
+++ turbogears/tests/test_form_controllers.py	(working copy)
@@ -34,6 +34,7 @@
         self.name = name
         self.age = age
         self.date = date
+        return dict()
 
     @expose()
     @validate(form=myform)
@@ -43,80 +44,91 @@
         self.name = name
         self.age = age
         self.date = date
+        return dict()
 
-def test_form_translation():
-    """Form input is translated into properly converted parameters"""
-    root = MyRoot()
-    cherrypy.root = root
-    testutil.create_request("/testform?name=ed&date=11/05/2005&age=5")
-    assert root.name == "ed"
-    assert root.age == 5
 
-def test_form_translation_new_style():
-    """Form input is translated into properly converted parameters"""
-    root = MyRoot()
-    cherrypy.root = root
-    testutil.create_request("/testform_new_style?name=ed&date=11/05/2005&age=5&")
-    assert root.name == "ed"
-    assert root.age == 5
+class TestFormControllers(testutil.TGWebTest):
 
-def test_invalid_form_with_error_handling():
-    """Invalid forms can be handled by the method"""
-    root = cherrypy.root
-    testutil.create_request("/testform?name=ed&age=edalso&date=11/05/2005")
-    assert root.has_errors
+    def test_form_translation(self):
+        """Form input is translated into properly converted parameters"""
+        root = MyRoot()
+        cherrypy.root = root
+        self.app.get("/testform?name=ed&date=11/05/2005&age=5")
+        assert root.name == "ed"
+        assert root.age == 5
 
-def test_css_should_appear():
-    """CSS should appear when asked for"""
-    testutil.create_request("/")
-    assert "calendar-system.css" in cherrypy.response.body[0]
+    def test_form_translation_new_style(self):
+        """Form input is translated into properly converted parameters"""
+        root = MyRoot()
+        cherrypy.root = root
+        self.app.get("/testform_new_style?name=ed&date=11/05/2005&age=5&")
+        assert root.name == "ed"
+        assert root.age == 5
 
-def test_javascript_should_appear():
-    """JavaScript should appear when asked for"""
-    testutil.create_request("/")
-    assert "calendar.js" in cherrypy.response.body[0]
+    def test_invalid_form_with_error_handling(self):
+        """Invalid forms can be handled by the method"""
+        root = cherrypy.root
+        self.app.get("/testform?name=ed&age=edalso&date=11/05/2005")
+        assert root.has_errors
 
-def test_include_mochikit():
-    """JSLinks (and MochiKit especially) can be included easily"""
-    testutil.create_request("/usemochi")
-    assert "MochiKit.js" in cherrypy.response.body[0]
+    def test_css_should_appear(self):
+        """CSS should appear when asked for"""
+        cherrypy.root = MyRoot()
+        response = self.app.get("/")
+        assert "calendar-system.css" in response
 
-def test_suppress_mochikit():
-    """MochiKit inclusion can be suppressed"""
-    config.update({"global": {"tg.mochikit_suppress": True}})
-    testutil.create_request("/usemochi")
-    suppressed_body = cherrypy.response.body[0]
-    # repair the fixture
-    config.update({"global": {"tg.mochikit_suppress": False}})
-    testutil.create_request("/usemochi")
-    included_body = cherrypy.response.body[0]
-    assert "MochiKit.js" not in suppressed_body
-    assert "MochiKit.js" in included_body
+    def test_javascript_should_appear(self):
+        """JavaScript should appear when asked for"""
+        cherrypy.root = MyRoot()
+        response = self.app.get("/")
+        assert "calendar.js" in response
 
-def test_mochikit_everywhere():
-    """MochiKit can be included everywhere by setting tg.mochikit_all"""
-    config.update({"global": {"tg.mochikit_all": True}})
-    testutil.create_request("/")
-    config.update({"global": {"tg.mochikit_all": False}})
-    assert "MochiKit.js" in cherrypy.response.body[0]
+    def test_include_mochikit(self):
+        """JSLinks (and MochiKit especially) can be included easily"""
+        cherrypy.root = MyRoot()
+        response = self.app.get("/usemochi")
+        assert "MochiKit.js" in response
 
-def test_mochikit_nowhere():
-    """Setting tg.mochikit_suppress will prevent including it everywhere"""
-    config.update({"global": {"tg.mochikit_all": True}})
-    config.update({"global": {"tg.mochikit_suppress": True}})
-    testutil.create_request("/")
-    config.update({"global": {"tg.mochikit_all": False}})
-    config.update({"global": {"tg.mochikit_suppress": False}})
-    assert "MochiKit.js" not in cherrypy.response.body[0]
+    def test_suppress_mochikit(self):
+        """MochiKit inclusion can be suppressed"""
+        cherrypy.root = MyRoot()
+        config.update({"global": {"tg.mochikit_suppress": True}})
+        response = self.app.get("/usemochi")
+        suppressed_body = response.body
+        # repair the fixture
+        config.update({"global": {"tg.mochikit_suppress": False}})
+        response = self.app.get("/usemochi")
+        included_body = response.body
+        assert "MochiKit.js" not in suppressed_body
+        assert "MochiKit.js" in included_body
 
-def test_include_widgets():
-    """Any widget can be included everywhere by setting tg.include_widgets"""
-    config.update({"global": {"tg.include_widgets": ["mochikit"]}})
-    testutil.create_request("/")
-    config.update({"global": {"tg.include_widgets": []}})
-    assert "MochiKit.js" in cherrypy.response.body[0]
+    def test_mochikit_everywhere(self):
+        """MochiKit can be included everywhere by setting tg.mochikit_all"""
+        cherrypy.root = MyRoot()
+        config.update({"global": {"tg.mochikit_all": True}})
+        response = self.app.get("/")
+        config.update({"global": {"tg.mochikit_all": False}})
+        assert "MochiKit.js" in response
 
+    def test_mochikit_nowhere(self):
+        """Setting tg.mochikit_suppress will prevent including it everywhere"""
+        cherrypy.root = MyRoot()
+        config.update({"global": {"tg.mochikit_all": True}})
+        config.update({"global": {"tg.mochikit_suppress": True}})
+        response = self.app.get("/")
+        config.update({"global": {"tg.mochikit_all": False}})
+        config.update({"global": {"tg.mochikit_suppress": False}})
+        assert "MochiKit.js" not in response
 
+    def test_include_widgets(self):
+        """Any widget can be included everywhere by setting tg.include_widgets"""
+        cherrypy.root = MyRoot()
+        config.update({"global": {"tg.include_widgets": ["mochikit"]}})
+        response = self.app.get("/")
+        config.update({"global": {"tg.include_widgets": []}})
+        assert "MochiKit.js" in response
+
+
 class State(object):
     counter = 0
 
Index: turbogears/tests/test_errorhandling.py
===================================================================
--- turbogears/tests/test_errorhandling.py	(revision 4618)
+++ turbogears/tests/test_errorhandling.py	(working copy)
@@ -208,133 +208,134 @@
         return dict(title="Nested")
 
 
-class TestErrorHandler(unittest.TestCase):
+class TestErrorHandler(testutil.TGWebTest):
 
     def setUp(self):
+        testutil.TGWebTest.setUp(self)
         cherrypy.root = MyRoot()
         cherrypy.root.nestedcontroller = NestedController()
 
     def test_defaultErrorHandler(self):
         """Default error handler."""
-        testutil.create_request("/defaulterror?bar=abc")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
-        testutil.create_request("/defaulterror?bar=true")
-        self.failUnless("Default error provider" in cherrypy.response.body[0])
+        response = self.app.get("/defaulterror?bar=abc")
+        self.failUnless("Default error handler" in response)
+        response = self.app.get("/defaulterror?bar=true")
+        self.failUnless("Default error provider" in response)
 
     def test_specialisedErrorHandler(self):
         """Error handler specialisation."""
-        testutil.create_request("/specialisederror?bar=abc&baz=a@b.com")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
-        testutil.create_request("/specialisederror?baz=abc&bar=1")
+        response = self.app.get("/specialisederror?bar=abc&baz=a@b.com")
+        self.failUnless("Default error handler" in response)
+        response = self.app.get("/specialisederror?baz=abc&bar=1")
         self.failUnless("Specialised error handler" in
-                        cherrypy.response.body[0])
-        testutil.create_request("/specialisederror?bar=1&baz=a@b.com")
+                        response)
+        response = self.app.get("/specialisederror?bar=1&baz=a@b.com")
         self.failUnless("Specialised error provider" in
-                        cherrypy.response.body[0])
+                        response)
 
     def test_exceptionErrorHandler(self):
         """Error handler for exceptions."""
-        testutil.create_request("/exceptionerror")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
+        response = self.app.get("/exceptionerror")
+        self.failUnless("Default error handler" in response)
 
     def test_recursiveErrorHandler(self):
         """Recursive error handler."""
-        testutil.create_request("/recursiveerror?bar=abc")
-        self.failUnless("Recursive error handler" in cherrypy.response.body[0])
-        testutil.create_request("/recursiveerror?bar=1")
+        response = self.app.get("/recursiveerror?bar=abc")
+        self.failUnless("Recursive error handler" in response)
+        response = self.app.get("/recursiveerror?bar=1")
         self.failUnless("Recursive error provider" in
-                        cherrypy.response.body[0])
+                        response)
 
     def test_implicitErrorHandler(self):
         """Implicit error handling."""
-        testutil.create_request("/impliciterror?bar=abc")
+        response = self.app.get("/impliciterror?bar=abc")
         self.failUnless("Implicit error handler" in
-                        cherrypy.response.body[0])
-        testutil.create_request("/impliciterror?bar=1")
+                        response)
+        response = self.app.get("/impliciterror?bar=1")
         self.failUnless("Implicit error provider" in
-                        cherrypy.response.body[0])
+                        response)
 
     def test_normalMethodErrorHandler(self):
         """Normal method as an error handler."""
-        testutil.create_request("/normalmethodcaller?bar=abc")
-        self.failUnless("Normal method" in cherrypy.response.body[0])
-        testutil.create_request("/normalmethodcaller?bar=true")
-        self.failUnless("Normal method caller" in cherrypy.response.body[0])
+        response = self.app.get("/normalmethodcaller?bar=abc")
+        self.failUnless("Normal method" in response)
+        response = self.app.get("/normalmethodcaller?bar=true")
+        self.failUnless("Normal method caller" in response)
 
     def test_infiniteRecursionPrevention(self):
         """Infinite recursion prevention."""
-        testutil.create_request("/infiniteloop")
-        self.failUnless("Exception 2" in cherrypy.response.body[0])
+        response = self.app.get("/infiniteloop")
+        self.failUnless("Exception 2" in response)
 
     def test_positionalArgs(self):
         """Positional argument validation."""
-        testutil.create_request("/positionalargs/first/23/third?bar=abc")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
-        testutil.create_request("/positionalargs/first/abc/third?bar=false")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
-        testutil.create_request("/positionalargs/first/abc/third?bar=abc")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
-        testutil.create_request("/positionalargs/first/23/third?bar=true")
-        self.failUnless("Positional arguments" in cherrypy.response.body[0])
+        response = self.app.get("/positionalargs/first/23/third?bar=abc")
+        self.failUnless("Default error handler" in response)
+        response = self.app.get("/positionalargs/first/abc/third?bar=false")
+        self.failUnless("Default error handler" in response)
+        response = self.app.get("/positionalargs/first/abc/third?bar=abc")
+        self.failUnless("Default error handler" in response)
+        response = self.app.get("/positionalargs/first/23/third?bar=true")
+        self.failUnless("Positional arguments" in response)
         self.failUnless(cherrypy.root.first == "first")
         self.failUnless(cherrypy.root.second == 23)
         self.failUnless(cherrypy.root.third == "third")
 
     def test_missingArgs(self):
         """Arguments required in validation missing."""
-        testutil.create_request("/missingargs")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
-        testutil.create_request("/missingargs?bar=12")
-        self.failUnless("Missing args provider" in cherrypy.response.body[0])
+        response = self.app.get("/missingargs")
+        self.failUnless("Default error handler" in response)
+        response = self.app.get("/missingargs?bar=12")
+        self.failUnless("Missing args provider" in response)
 
     def test_nohandler(self):
         """No error hanlder declared."""
-        testutil.create_request("/nohandler")
-        self.failUnless("Exception raised" in cherrypy.response.body[0])
+        response = self.app.get("/nohandler")
+        self.failUnless("Exception raised" in response)
 
     def test_bindArgs(self):
         """Arguments can be bond to an error handler."""
-        testutil.create_request("/bindargs")
-        self.failUnless("123" in cherrypy.response.body[0])
+        response = self.app.get("/bindargs")
+        self.failUnless("123" in response)
 
     def test_notExposed(self):
         """Validation error handling is decoupled from expose."""
-        testutil.create_request("/notexposedcaller?foo=a&bar=rab&baz=c")
-        self.failUnless("Not exposed error" in cherrypy.response.body[0])
-        self.failUnless("rab" in cherrypy.response.body[0])
+        response = self.app.get("/notexposedcaller?foo=a&bar=rab&baz=c")
+        self.failUnless("Not exposed error" in response)
+        self.failUnless("rab" in response)
 
     def test_continuations(self):
         """Continuations via error handling mechanism."""
-        testutil.create_request("/continuationcaller?bar=a")
-        self.failUnless("Continuation caller" in cherrypy.response.body[0])
+        response = self.app.get("/continuationcaller?bar=a")
+        self.failUnless("Continuation caller" in response)
         self.failUnless(cherrypy.root.continuation == True)
 
     def test_nested(self):
         """Potentially ambiguous cases."""
-        testutil.create_request("/nest?bar=a")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
-        testutil.create_request("/nestedcontroller/nest?bar=a")
-        self.failUnless("Nested" in cherrypy.response.body[0])
+        response = self.app.get("/nest?bar=a")
+        self.failUnless("Default error handler" in response)
+        response = self.app.get("/nestedcontroller/nest?bar=a")
+        self.failUnless("Nested" in response)
 
     def test_failsafe(self):
         """Failsafe values for erroneous input."""
-        testutil.create_request("/failsafenone?bar=a&baz=b")
-        self.failUnless('"bar": "a"' in cherrypy.response.body[0])
-        self.failUnless('"baz": "b"' in cherrypy.response.body[0])
-        testutil.create_request("/failsafevaluesdict?bar=a&baz=b")
-        self.failUnless('"bar": 1' in cherrypy.response.body[0])
-        self.failUnless('"baz": 2' in cherrypy.response.body[0])
-        testutil.create_request("/failsafevaluesatom?bar=a&baz=b")
-        self.failUnless('"bar": 13' in cherrypy.response.body[0])
-        self.failUnless('"baz": 13' in cherrypy.response.body[0])
-        testutil.create_request("/failsafemaperrors?bar=a&baz=b")
+        response = self.app.get("/failsafenone?bar=a&baz=b")
+        self.failUnless('"bar": "a"' in response)
+        self.failUnless('"baz": "b"' in response)
+        response = self.app.get("/failsafevaluesdict?bar=a&baz=b")
+        self.failUnless('"bar": 1' in response)
+        self.failUnless('"baz": 2' in response)
+        response = self.app.get("/failsafevaluesatom?bar=a&baz=b")
+        self.failUnless('"bar": 13' in response)
+        self.failUnless('"baz": 13' in response)
+        response = self.app.get("/failsafemaperrors?bar=a&baz=b")
         self.failUnless('"bar": "Please enter an integer value"' in
-                        cherrypy.response.body[0])
+                        response)
         self.failUnless('"baz": "Please enter an integer value"' in
-                        cherrypy.response.body[0])
-        testutil.create_request("/failsafeformencode?bar=a&baz=b")
-        self.failUnless('"bar": 1' in cherrypy.response.body[0])
-        self.failUnless('"baz": 2' in cherrypy.response.body[0])
-        testutil.create_request("/failsafedefaults?bar=a&baz=b")
-        self.failUnless('"bar": 1' in cherrypy.response.body[0])
-        self.failUnless('"baz": 2' in cherrypy.response.body[0])
+                        response)
+        response = self.app.get("/failsafeformencode?bar=a&baz=b")
+        self.failUnless('"bar": 1' in response)
+        self.failUnless('"baz": 2' in response)
+        response = self.app.get("/failsafedefaults?bar=a&baz=b")
+        self.failUnless('"bar": 1' in response,response)
+        self.failUnless('"baz": 2' in response)
Index: turbogears/tests/test_sqlalchemy.py
===================================================================
--- turbogears/tests/test_sqlalchemy.py	(revision 4618)
+++ turbogears/tests/test_sqlalchemy.py	(working copy)
@@ -9,7 +9,7 @@
 from turbogears.database import metadata, session, mapper, \
     bind_metadata, get_metadata
 from turbogears.controllers import RootController
-from turbogears.testutil import create_request, sqlalchemy_cleanup, \
+from turbogears.testutil import sqlalchemy_cleanup, TGWebTest, \
     capture_log, print_log
 
 
@@ -162,95 +162,91 @@
         return "No exceptions occurred"
 
 
-def test_implicit_trans_no_error():
-    """If a controller runs sucessfully, the transaction is commited."""
-    capture_log("turbogears.database")
-    cherrypy.root = MyRoot()
-    create_request("/no_error?name=A.%20Dent")
-    output = cherrypy.response.body[0]
-    print output
-    print_log()
-    q = session.query(Person)
-    arthur = q.filter_by(name="A. Dent").first()
-    assert 'someconfirmhandler' in output, \
-        'The no error should have redirected to someconfirmhandler'
-    assert arthur is not None, 'Person arthur should have been saved!'
-    assert arthur.name == "A. Dent", 'Person arthur should be named "A. Dent"'
+class TestSQLAlchemy(TGWebTest):
 
-def test_raise_sa_exception():
-    """If a controller causes an SA exception, it is raised properly."""
-    capture_log("turbogears.database")
-    cherrypy.root = MyRoot()
-    create_request("/create_person?id=20")
-    output = cherrypy.response.body[0]
-    print_log()
-    print output
-    assert 'No exceptions occurred' in output
-    assert cherrypy.response.status.startswith("200")
-    create_request("/create_person?id=20")
-    output = cherrypy.response.body[0]
-    assert cherrypy.response.status.startswith("500")
-    print output
-    assert 'KARL25' not in output, \
-        'Exception should NOT have been handled by our handler'
-    assert 'DBAPIError' in output, \
-        'The page should have displayed an SQLAlchemy exception'
+    def test_implicit_trans_no_error(self):
+        """If a controller runs sucessfully, the transaction is commited."""
+        #capture_log("turbogears.database")
+        cherrypy.root = MyRoot()
+        response = self.app.get("/no_error?name=A.%20Dent")
+        output = response.body
+        #print output
+        #print_log()
+        q = session.query(Person)
+        arthur = q.filter_by(name="A. Dent").first()
+        assert 'someconfirmhandler' in output, \
+            'The no error should have redirected to someconfirmhandler'
+        assert arthur is not None, 'Person arthur should have been saved!'
+        assert arthur.name == "A. Dent", 'Person arthur should be named "A. Dent"'
 
-def test_user_exception():
-    """If a controller raises an exception, transactions are rolled back."""
-    capture_log("turbogears.database")
-    cherrypy.root = MyRoot()
-    create_request("/create_person?id=19&docom=0&doerr=1&name=Martin%20GAL")
-    output = cherrypy.response.body[0]
-    print_log()
-    print output
-    assert 'KARL25' in output, \
-        'The exception handler should have answered us'
-    p = session.query(Person).get(19)
-    assert p is None, \
-        'This Person should have been rolled back: %s' % p
+    def test_raise_sa_exception(self):
+        """If a controller causes an SA exception, it is raised properly."""
+        #capture_log("turbogears.database")
+        cherrypy.root = MyRoot()
+        response = self.app.get("/create_person?id=20", status=200)
+        output = response.body
+        #print_log()
+        print output
+        assert 'No exceptions occurred' in output
+        response = self.app.get("/create_person?id=20", status=500)
+        output = response.body
+        print output
+        assert 'KARL25' not in output, \
+            'Exception should NOT have been handled by our handler'
+        assert 'DBAPIError' in output, \
+            'The page should have displayed an SQLAlchemy exception'
 
-def test_user_redirect():
-    """If a controller redirects, transactions are committed."""
-    cherrypy.root = MyRoot()
-    create_request("/create_person?id=22&doerr=2")
-    assert session.query(Person).get(22) is not None, \
-        'The controller only redirected, the Person should have been saved'
+    def test_user_exception(self):
+        """If a controller raises an exception, transactions are rolled back."""
+        #capture_log("turbogears.database")
+        cherrypy.root = MyRoot()
+        response = self.app.get("/create_person?id=19&docom=0&doerr=1&name=Martin%20GAL")
+        output = response.body
+        #print_log()
+        print output
+        assert 'KARL25' in output, \
+            'The exception handler should have answered us'
+        p = session.query(Person).get(19)
+        assert p is None, \
+            'This Person should have been rolled back: %s' % p
 
-def test_cntrl_commit():
-    """It's safe to commit a transaction in the controller."""
-    cherrypy.root = MyRoot()
-    create_request("/create_person?id=23&docom=1")
-    assert 'InvalidRequestError' not in cherrypy.response.body[0]
-    assert session.query(Person).get(23) is not None, \
-        'The Person 23 should have been saved during commit inside controller'
+    def test_user_redirect(self):
+        """If a controller redirects, transactions are committed."""
+        cherrypy.root = MyRoot()
+        self.app.get("/create_person?id=22&doerr=2")
+        assert session.query(Person).get(22) is not None, \
+            'The controller only redirected, the Person should have been saved'
 
-def test_cntrl_commit2():
-    """A commit inside the controller is not rolled back by the exception."""
-    cherrypy.root = MyRoot()
-    create_request("/create_person?id=24&docom=1&doerr=1")
-    assert 'InvalidRequestError' not in cherrypy.response.body[0]
-    assert session.query(Person).get(24) is not None, \
-        'The Person 24 should have been saved during commit' \
-        ' inside controller and not rolled back'
+    def test_cntrl_commit(self):
+        """It's safe to commit a transaction in the controller."""
+        cherrypy.root = MyRoot()
+        response = self.app.get("/create_person?id=23&docom=1")
+        assert 'InvalidRequestError' not in response
+        assert session.query(Person).get(23) is not None, \
+            'The Person 23 should have been saved during commit inside controller'
 
-def test_cntrl_flush():
-    """It's safe to flush in the controller."""
-    cherrypy.root = MyRoot()
-    create_request("/create_person?id=25&doflush=1")
-    print cherrypy.response.body[0]
-    assert 'No exceptions occurred' in cherrypy.response.body[0]
-    create_request("/create_person?id=25&doflush=0")
-    assert cherrypy.response.status.startswith("500")
-    assert 'IntegrityError' in cherrypy.response.body[0]
-    create_request("/create_person?id=25&doflush=1")
-    assert cherrypy.response.status.startswith("200")
-    assert 'IntegrityError' in cherrypy.response.body[0]
-    create_request("/create_person?id=25&doflush=2")
-    assert cherrypy.response.status.startswith("200")
-    assert 'No exceptions occurred' in cherrypy.response.body[0]
+    def test_cntrl_commit2(self):
+        """A commit inside the controller is not rolled back by the exception."""
+        cherrypy.root = MyRoot()
+        response = self.app.get("/create_person?id=24&docom=1&doerr=1")
+        assert 'InvalidRequestError' not in response
+        assert session.query(Person).get(24) is not None, \
+            'The Person 24 should have been saved during commit' \
+            ' inside controller and not rolled back'
 
+    def test_cntrl_flush(self):
+        """It's safe to flush in the controller."""
+        cherrypy.root = MyRoot()
+        response = self.app.get("/create_person?id=25&doflush=1")
+        assert 'No exceptions occurred' in response
+        response = self.app.get("/create_person?id=25&doflush=0", status=500)
+        assert 'IntegrityError' in response
+        response = self.app.get("/create_person?id=25&doflush=1")
+        assert 'IntegrityError' in response
+        response = self.app.get("/create_person?id=25&doflush=2")
+        assert 'No exceptions occurred' in response
 
+
 # Exception handling with rollback
 
 class RbRoot(RootController):
@@ -292,44 +288,41 @@
             session.rollback()
         raise Exception('test')
 
+class TestExceptionRollback(TGWebTest):
 
-def test_exc_rollback():
-    """An exception within a controller method causes a rollback.
+    def test_exc_rollback(self):
+        """An exception within a controller method causes a rollback.
 
-    Try to create a user that should rollback because of an exception
-    so user 25 should not exist, but user 26 should be present since it
-    is created by the exception handler.
+        Try to create a user that should rollback because of an exception
+        so user 25 should not exist, but user 26 should be present since it
+        is created by the exception handler.
 
-    """
-    cherrypy.root = RbRoot()
-    create_request('/doerr?id=26')
-    output = cherrypy.response.body[0]
-    print output
-    assert 'KARL27' in output, 'Exception handler should have answered'
-    assert session.query(User).get(26) is None
-    assert session.query(User).get(27) is not None
+        """
+        cherrypy.root = RbRoot()
+        response = self.app.get('/doerr?id=26')
+        assert 'KARL27' in response, 'Exception handler should have answered'
+        assert session.query(User).get(26) is None
+        assert session.query(User).get(27) is not None
 
-def test_exc_rollback2():
-    """An exception within a controller method causes a rollback.
+    def test_exc_rollback2(self):
+        """An exception within a controller method causes a rollback.
 
-    Try to create a user that should rollback because of an exception
-    so user XX should not exist.
+        Try to create a user that should rollback because of an exception
+        so user XX should not exist.
 
-    """
-    cherrypy.root = RbRoot()
-    create_request('/doerr?id=XX')
-    output = cherrypy.response.body[0]
-    assert 'KARL27' in output, 'Exception handler should have answered'
-    assert session.query(User).get('XX') is None
+        """
+        cherrypy.root = RbRoot()
+        response = self.app.get('/doerr?id=XX')
+        assert 'KARL27' in response, 'Exception handler should have answered'
+        assert session.query(User).get('XX') is None
 
-def test_exc_done_rollback():
-    """No problems with error handler if controller manually rollbacks."""
-    cherrypy.root = RbRoot()
-    create_request('/doerr?id=28&dorb=1')
-    output = cherrypy.response.body[0]
-    assert 'KARL27' in output, 'Exception handler should have answered'
-    assert session.query(User).get(28) is None
-    assert session.query(User).get(29) is not None
+    def test_exc_done_rollback(self):
+        """No problems with error handler if controller manually rollbacks."""
+        cherrypy.root = RbRoot()
+        response = self.app.get('/doerr?id=28&dorb=1')
+        assert 'KARL27' in response, 'Exception handler should have answered'
+        assert session.query(User).get(28) is None
+        assert session.query(User).get(29) is not None
 
 
 # Session freshness tests
@@ -352,27 +345,28 @@
         assert session.query(Test).get(1).val == 'b'
         return dict()
 
+class TestSessionFreshness(TGWebTest):
 
-def test_session_freshness():
-    """Check for session freshness.
+    def test_session_freshness(self):
+        """Check for session freshness.
 
-    Changes made to the data in thread B should be reflected in thread A.
+        Changes made to the data in thread B should be reflected in thread A.
 
-    """
-    test_table.insert().execute(dict(id=1, val='a'))
-    cherrypy.root = FreshRoot()
-    create_request("/test1")
-    assert cherrypy.response.status.startswith("200")
-    assert 'AssertionError' not in cherrypy.response.body[0]
-    # Call test2 in a different thread
-    class ThreadB(threading.Thread):
-        def run(self):
-            create_request("/test2")
-            assert cherrypy.response.status.startswith("200")
-            assert 'AssertionError' not in cherrypy.response.body[0]
-    thrdb = ThreadB()
-    thrdb.start()
-    thrdb.join()
-    create_request("/test3")
-    assert cherrypy.response.status.startswith("200")
-    assert 'AssertionError' not in cherrypy.response.body[0]
+        """
+        test_table.insert().execute(dict(id=1, val='a'))
+        cherrypy.root = FreshRoot()
+        response = self.app.get("/test1")
+        assert 'AssertionError' not in response
+        # Call test2 in a different thread
+        class ThreadB(threading.Thread):
+            def __init__(self, app):
+                threading.Thread.__init__(self)
+                self.app = app
+            def run(self):
+                response = self.app.get("/test2")
+                assert 'AssertionError' not in response
+        thrdb = ThreadB(app=self.app)
+        thrdb.start()
+        thrdb.join()
+        response = self.app.get("/test3")
+        assert 'AssertionError' not in response
Index: turbogears/tests/test_expose.py
===================================================================
--- turbogears/tests/test_expose.py	(revision 4618)
+++ turbogears/tests/test_expose.py	(working copy)
@@ -1,8 +1,7 @@
 import cherrypy
 import simplejson
 
-from turbogears import controllers, expose
-from turbogears.testutil import create_request
+from turbogears import controllers, expose, testutil
 
 
 class ExposeRoot(controllers.RootController):
@@ -19,55 +18,50 @@
         return dict(title="Foobar", mybool=False, someval="foo")
 
 
-def test_gettinghtml():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json")
-    body = cherrypy.response.body[0]
-    assert "Paging all foo" in body
+class TestExposeRoot(testutil.TGWebTest):
 
-def test_gettingjson():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json?tg_format=json")
-    body = cherrypy.response.body[0]
-    assert '"title": "Foobar"' in body
+    def test_gettinghtml(self):
+        cherrypy.root = ExposeRoot()
+        response = self.app.get("/with_json")
+        assert "Paging all foo" in response
 
-def test_gettingjsonviaaccept():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json_via_accept",
-            headers=dict(Accept="text/javascript"))
-    body = cherrypy.response.body[0]
-    assert '"title": "Foobar"' in body
+    def test_gettingjson(self):
+        cherrypy.root = ExposeRoot()
+        response = self.app.get("/with_json?tg_format=json")
+        assert '"title": "Foobar"' in response
 
-def test_getting_json_with_accept_but_using_tg_format():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json_via_accept?tg_format=json")
-    body = cherrypy.response.body[0]
-    assert '"title": "Foobar"' in body
+    def test_gettingjsonviaaccept(self):
+        cherrypy.root = ExposeRoot()
+        response = self.app.get("/with_json_via_accept",
+                headers=dict(Accept="text/javascript"))
+        assert '"title": "Foobar"' in response
 
-def test_getting_plaintext():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json_via_accept",
-        headers=dict(Accept="text/plain"))
-    print cherrypy.response.body[0]
-    assert cherrypy.response.body[0] == "This is a plain text for foo."
+    def test_getting_json_with_accept_but_using_tg_format(self):
+        cherrypy.root = ExposeRoot()
+        response = self.app.get("/with_json_via_accept?tg_format=json")
+        assert '"title": "Foobar"' in response
 
-def test_allow_json():
+    def test_getting_plaintext(self):
+        cherrypy.root = ExposeRoot()
+        response = self.app.get("/with_json_via_accept",
+                headers=dict(Accept="text/plain"))
+        assert response.body == "This is a plain text for foo."
 
-    class NewRoot(controllers.RootController):
-        @expose(template="turbogears.tests.doesnotexist", allow_json=True)
-        def test(self):
-            return dict(title="Foobar", mybool=False, someval="niggles")
+    def test_allow_json(self):
 
-    cherrypy.root = NewRoot()
-    create_request("/test", headers= dict(accept="text/javascript"))
-    body = cherrypy.response.body[0]
-    values = simplejson.loads(body)
-    assert values == dict(title="Foobar", mybool=False, someval="niggles",
-        tg_flash=None)
-    assert cherrypy.response.headers["Content-Type"] == "text/javascript"
-    create_request("/test?tg_format=json")
-    body = cherrypy.response.body[0]
-    values = simplejson.loads(body)
-    assert values == dict(title="Foobar", mybool=False, someval="niggles",
-        tg_flash=None)
-    assert cherrypy.response.headers["Content-Type"] == "text/javascript"
+        class NewRoot(controllers.RootController):
+            @expose(template="turbogears.tests.doesnotexist", allow_json=True)
+            def test(self):
+                return dict(title="Foobar", mybool=False, someval="niggles")
+
+        cherrypy.root = NewRoot()
+        response = self.app.get("/test", headers= dict(accept="text/javascript"))
+        values = simplejson.loads(response.body)
+        assert values == dict(title="Foobar", mybool=False, someval="niggles",
+            tg_flash=None)
+        assert response.content_type == "text/javascript"
+        response = self.app.get("/test?tg_format=json")
+        values = simplejson.loads(response.body)
+        assert values == dict(title="Foobar", mybool=False, someval="niggles",
+            tg_flash=None)
+        assert response.content_type == "text/javascript"
Index: turbogears/tests/test_controllers.py
===================================================================
--- turbogears/tests/test_controllers.py	(revision 4618)
+++ turbogears/tests/test_controllers.py	(working copy)
@@ -1,4 +1,5 @@
 import unittest
+import simplejson
 import formencode
 import cherrypy
 import pkg_resources
@@ -18,7 +19,7 @@
 
     @expose()
     def index(self):
-        pass
+        return dict()
 
     def validation_error_handler(self, tg_source, tg_errors, *args, **kw):
         self.functionname = tg_source.__name__
@@ -48,8 +49,11 @@
                 "turbogears.tests", "test_controllers.py"))
 
     @expose()
-    def unicode(self):
-        cherrypy.response.headers["Content-Type"] = "text/html"
+    def unicode(self, response=None):
+        # TODO: fix me?  make response a strict parameter? or can we use cp safely
+        if response is None:
+            response = cherrypy.response
+        response.headers["Content-Type"] = "text/html"
         return u'\u00bfHabla espa\u00f1ol?'
 
     @expose()
@@ -171,6 +175,14 @@
     def flash_redirected(self):
         return dict(title="Foobar", mybool=False, someval="niggles")
 
+    @expose()
+    def redirect(self):
+        redirect("/foo")
+
+    @expose()
+    def raise_redirect(self):
+        raise redirect("/foo")
+
     @expose(html="turbogears.tests.simple", allow_json=True)
     def flash_redirect_with_trouble_chars(self):
         flash(u"$foo, k\xe4se;\tbar!")
@@ -220,41 +232,39 @@
         return "redirected OK"
 
 
-class TestRoot(unittest.TestCase):
+class TestRoot(testutil.TGWebTest):
 
     def setUp(self):
+        testutil.TGWebTest.setUp(self)
         cherrypy.root = None
         cherrypy.tree.mount_points = {}
         cherrypy.tree.mount(MyRoot(), "/")
         cherrypy.tree.mount(SubApp(), "/subthing")
 
     def tearDown(self):
+        testutil.TGWebTest.tearDown(self)
         cherrypy.root = None
         cherrypy.tree.mount_points = {}
 
     def test_js_files(self):
         """Can access the JavaScript files"""
-        testutil.create_request("/tg_js/MochiKit.js")
-        assert cherrypy.response.headers[
-            "Content-Type"] == "application/x-javascript"
-        assert cherrypy.response.status == "200 OK"
+        response = self.app.get("/tg_js/MochiKit.js", status=200)
+        assert response.headers["Content-Type"] == "application/x-javascript"
 
     def test_json_output(self):
-        testutil.create_request("/test?tg_format=json")
-        import simplejson
-        values = simplejson.loads(cherrypy.response.body[0])
+        response = self.app.get("/test?tg_format=json")
+        values = simplejson.loads(response.body)
         assert values == dict(title="Foobar", mybool=False,
             someval="niggles", tg_flash=None)
-        assert cherrypy.response.headers["Content-Type"] == "text/javascript"
+        assert response.headers["Content-Type"] == "text/javascript"
 
     def test_implied_json(self):
-        testutil.create_request("/impliedjson?tg_format=json")
-        assert '"title": "Blah"' in cherrypy.response.body[0]
+        response = self.app.get("/impliedjson?tg_format=json")
+        assert '"title": "Blah"' in response
 
     def test_allow_json(self):
-        testutil.create_request("/allowjson?tg_format=json")
-        assert cherrypy.response.status.startswith("500")
-        assert cherrypy.response.headers["Content-Type"] == "text/html"
+        response = self.app.get("/allowjson?tg_format=json", status=500)
+        assert response.headers["Content-Type"] == "text/html"
 
     def test_allow_json_config(self):
         """JSON output can be enabled via config."""
@@ -265,8 +275,8 @@
                 return dict(title="Foobar", mybool=False, someval="foo",
                      tg_html="turbogears.tests.simple")
         cherrypy.root = JSONRoot()
-        testutil.create_request('/allowjsonconfig?tg_format=json')
-        assert cherrypy.response.headers["Content-Type"] == "text/javascript"
+        response = self.app.get('/allowjsonconfig?tg_format=json')
+        assert response.headers["Content-Type"] == "text/javascript"
         config.update({'tg.allow_json': False})
 
     def test_allow_json_config_false(self):
@@ -278,9 +288,8 @@
                 return dict(title="Foobar", mybool=False, someval="foo",
                      tg_html="turbogears.tests.simple")
         cherrypy.root = JSONRoot()
-        testutil.create_request('/allowjson?tg_format=json')
-        assert cherrypy.response.status.startswith("404")
-        assert cherrypy.response.headers["Content-Type"] == "text/html"
+        response = self.app.get('/allowjson?tg_format=json', status=404)
+        assert response.headers["Content-Type"] == "text/html"
         config.update({'tg.allow_json': False})
 
     def test_json_error(self):
@@ -293,211 +302,185 @@
         assert '"someval": "errors"' in cherrypy.response.body[0]
 
     def test_invalid_return(self):
-        testutil.create_request("/invalid")
-        assert cherrypy.response.status.startswith("500")
+        response = self.app.get("/invalid", status=500)
 
     def test_strict_parameters(self):
         config.update({"tg.strict_parameters": True})
-        testutil.create_request(
-            "/save?submit=save&firstname=Foo&lastname=Bar&badparam=1")
-        assert cherrypy.response.status.startswith("500")
+        response = self.app.get(
+            "/save?submit=save&firstname=Foo&lastname=Bar&badparam=1",
+            status=500)
         assert not hasattr(cherrypy.root, "errors")
 
     def test_throw_out_random(self):
         """Can append random value to the URL to avoid caching problems."""
-        testutil.create_request("/test?tg_random=1")
-        assert "Paging all niggles" in cherrypy.response.body[0]
+        response = self.app.get("/test?tg_random=1")
+        assert "Paging all niggles" in response
         config.update({"tg.strict_parameters": True})
-        testutil.create_request("/test?tg_random=1")
-        assert cherrypy.response.status.startswith("200")
-        assert "Paging all niggles" in cherrypy.response.body[0]
-        testutil.create_request("/test?tg_not_random=1")
-        assert cherrypy.response.status.startswith("500")
+        response = self.app.get("/test?tg_random=1", status=200)
+        assert "Paging all niggles" in response
+        response = self.app.get("/test?tg_not_random=1", status=500)
         assert not hasattr(cherrypy.root, "errors")
 
     def test_ignore_parameters(self):
         config.update({"tg.strict_parameters": True})
-        testutil.create_request("/test?ignore_me=1")
-        assert cherrypy.response.status.startswith("500")
+        response = self.app.get("/test?ignore_me=1", status=500)
         assert not hasattr(cherrypy.root, "errors")
         config.update({"tg.ignore_parameters": ['ignore_me', 'me_too']})
-        testutil.create_request("/test?ignore_me=1")
-        assert "Paging all niggles" in cherrypy.response.body[0]
-        testutil.create_request("/test?me_too=1")
-        assert cherrypy.response.status.startswith("200")
-        assert "Paging all niggles" in cherrypy.response.body[0]
-        testutil.create_request("/test?me_not=1")
-        assert cherrypy.response.status.startswith("500")
+        response = self.app.get("/test?ignore_me=1")
+        assert "Paging all niggles" in response
+        response = self.app.get("/test?me_too=1", status=200)
+        assert "Paging all niggles" in response
+        response = self.app.get("/test?me_not=1", status=500)
         assert not hasattr(cherrypy.root, "errors")
 
     def test_retrieve_dict_directly(self):
-        d = testutil.call(cherrypy.root.returnjson)
-        assert d["title"] == "Foobar"
+        response = self.app.get('/returnjson')
+        assert response.namespace["title"] == "Foobar"
 
     def test_templateOutput(self):
-        testutil.create_request("/test")
-        assert "Paging all niggles" in cherrypy.response.body[0]
+        response = self.app.get("/test")
+        assert "Paging all niggles" in response
 
     def test_safari_unicode_fix(self):
-        testutil.create_request("/unicode", headers={'User-Agent':
+        response = self.app.get("/unicode", headers={'User-Agent':
             "Apple WebKit Safari/412.2"})
-        firstline = cherrypy.response.body[0].split('\n')[0]
+        firstline = response.body.split('\n')[0]
         assert firstline == "&#xbf;Habla espa&#xf1;ol?"
         assert isinstance(firstline, str)
 
     def test_default_format(self):
         """The default format can be set via expose"""
-        testutil.create_request("/returnjson")
-        firstline = cherrypy.response.body[0]
-        assert '"title": "Foobar"' in firstline
-        testutil.create_request("/returnjson?tg_format=html")
-        assert cherrypy.response.status.startswith("500")
-        firstline = cherrypy.response.body[0]
-        assert '"title": "Foobar"' not in firstline
+        response = self.app.get("/returnjson")
+        assert '"title": "Foobar"' in response
+        response = self.app.get("/returnjson?tg_format=html", status=500)
 
     def test_content_type(self):
         """The content-type can be set via expose"""
-        testutil.create_request("/contenttype")
-        assert cherrypy.response.headers["Content-Type"] == "xml/atom"
+        response = self.app.get("/contenttype")
+        assert response.headers["Content-Type"] == "xml/atom"
 
     def test_returned_template_name(self):
-        testutil.create_request("/returnedtemplate")
-        data = cherrypy.response.body[0].lower()
+        response = self.app.get("/returnedtemplate")
+        data = response.body.lower()
         assert "<body>" in data
         assert 'groovy test template' in data
 
     def test_returned_template_short(self):
-        testutil.create_request("/returnedtemplate_short")
-        assert "Paging all foo" in cherrypy.response.body[0]
+        response = self.app.get("/returnedtemplate_short")
+        assert "Paging all foo" in response
 
     def test_expose_template_short(self):
-        testutil.create_request("/exposetemplate_short")
-        assert "Paging all foo" in cherrypy.response.body[0]
+        response = self.app.get("/exposetemplate_short")
+        assert "Paging all foo" in response
 
     def test_validation(self):
         """Data can be converted and validated"""
-        testutil.create_request("/istrue?value=true")
+        response = self.app.get("/istrue?value=true")
         assert cherrypy.root.value is True
-        testutil.create_request("/istrue?value=false")
+        response = self.app.get("/istrue?value=false")
         assert cherrypy.root.value is False
         cherrypy.root = MyRoot()
-        testutil.create_request("/istrue?value=foo")
+        response = self.app.get("/istrue?value=foo")
         assert not hasattr(cherrypy.root, "value")
         assert cherrypy.root.functionname == "istrue"
-        testutil.create_request("/save?submit=send&firstname=John&lastname=Doe")
+        response = self.app.get("/save?submit=send&firstname=John&lastname=Doe")
         assert cherrypy.root.fullname == "John Doe"
         assert cherrypy.root.submit == "send"
-        testutil.create_request("/save?submit=send&firstname=Arthur")
+        response = self.app.get("/save?submit=send&firstname=Arthur")
         assert cherrypy.root.fullname == "Arthur Miller"
-        testutil.create_request("/save?submit=send&firstname=Arthur&lastname=")
+        response = self.app.get("/save?submit=send&firstname=Arthur&lastname=")
         assert cherrypy.root.fullname == "Arthur "
-        testutil.create_request("/save?submit=send&firstname=D&lastname=")
+        response = self.app.get("/save?submit=send&firstname=D&lastname=")
         assert len(cherrypy.root.errors) == 1
         assert cherrypy.root.errors.has_key("firstname")
         assert "characters" in cherrypy.root.errors["firstname"].msg.lower()
-        testutil.create_request("/save?submit=send&firstname=&lastname=")
+        response = self.app.get("/save?submit=send&firstname=&lastname=")
         assert len(cherrypy.root.errors) == 1
         assert cherrypy.root.errors.has_key("firstname")
 
     def test_validation_chained(self):
         """Validation is not repeated if it already happened"""
         cherrypy.root.value = None
-        testutil.create_request("/errorchain?value=true")
+        self.app.get("/errorchain?value=true")
         assert cherrypy.root.value is None
-        testutil.create_request("/errorchain?value=notbool")
+        self.app.get("/errorchain?value=notbool")
         assert cherrypy.root.value == 'notbool'
 
     def test_validation_nested(self):
         """Validation is not repeated in nested method call"""
         cherrypy.root.value = None
-        testutil.create_request("/nestedcall?value=true")
+        response = self.app.get("/nestedcall?value=true")
         assert cherrypy.root.value == 'True'
-        testutil.create_request("/nestedcall?value=false")
+        self.app.get("/nestedcall?value=false")
         assert cherrypy.root.value == 'False'
 
     def test_validation_with_schema(self):
         """Data can be converted/validated with formencode.Schema instance"""
-        testutil.create_request("/save2?submit=send&firstname=Joe&lastname=Doe")
+        response = self.app.get("/save2?submit=send&firstname=Joe&lastname=Doe")
         assert cherrypy.root.fullname == "Joe Doe"
         assert cherrypy.root.submit == "send"
-        testutil.create_request("/save2?submit=send&firstname=Arthur&lastname=")
+        response = self.app.get("/save2?submit=send&firstname=Arthur&lastname=")
         assert cherrypy.root.fullname == "Arthur "
-        testutil.create_request("/save2?submit=send&firstname=&lastname=")
+        response = self.app.get("/save2?submit=send&firstname=&lastname=")
         assert len(cherrypy.root.errors) == 1
         assert cherrypy.root.errors.has_key("firstname")
-        testutil.create_request("/save2?submit=send&firstname=D&lastname=")
+        response = self.app.get("/save2?submit=send&firstname=D&lastname=")
         assert len(cherrypy.root.errors) == 1
         assert cherrypy.root.errors.has_key("firstname")
 
     def test_other_template(self):
         """'tg_html' in a returned dict will use the template specified there"""
-        testutil.create_request("/useother")
-        assert "This is the other template" in cherrypy.response.body[0]
+        response = self.app.get("/useother")
+        assert "This is the other template" in response
 
     def test_cheetah_template(self):
         """Cheetah templates can be used as well"""
-        testutil.create_request("/usecheetah")
-        body = cherrypy.response.body[0]
-        assert "This is the Cheetah test template." in body
-        assert "Paging all chimps." in body
+        response = self.app.get("/usecheetah")
+        assert "This is the Cheetah test template." in response
+        assert "Paging all chimps." in response
 
     def test_run_with_trans(self):
         """run_with_transaction is called only on topmost exposed method"""
         oldrwt = database.run_with_transaction
         database.run_with_transaction = cherrypy.root.rwt
-        testutil.create_request("/nestedcall?value=true")
+        self.app.get("/nestedcall?value=true")
         database.run_with_transaction = oldrwt
         assert cherrypy.root.value
         assert cherrypy.root.rwt_called == 1
 
     def test_positional(self):
         """Positional parameters should work"""
-        testutil.create_request("/pos/foo")
+        response = self.app.get("/pos/foo")
         assert cherrypy.root.posvalue == "foo"
 
     def test_flash_plain(self):
         """flash with strings should work"""
-        testutil.create_request("/flash_plain?tg_format=json")
-        import simplejson
-        values = simplejson.loads(cherrypy.response.body[0])
+        response = self.app.get("/flash_plain?tg_format=json")
+        values = simplejson.loads(response.body)
         assert values["tg_flash"] == "plain"
-        assert not cherrypy.response.simple_cookie.has_key("tg_flash")
+        assert not response.headers.has_key("tg_flash")
 
     def test_flash_unicode(self):
         """flash with unicode objects should work"""
-        testutil.create_request("/flash_unicode?tg_format=json")
-        import simplejson
-        values = simplejson.loads(cherrypy.response.body[0])
+        response = self.app.get("/flash_unicode?tg_format=json")
+        values = simplejson.loads(response.body)
         assert values["tg_flash"] == u"\xfcnicode"
-        assert not cherrypy.response.simple_cookie.has_key("tg_flash")
+        assert not response.headers.has_key("tg_flash")
 
     def test_flash_on_redirect(self):
         """flash must survive a redirect"""
-        testutil.create_request("/flash_redirect?tg_format=json")
-        assert cherrypy.response.status.startswith("302")
-        testutil.create_request(cherrypy.response.headers["Location"],
-            headers=dict(Cookie=cherrypy.response.simple_cookie.output(
-                header="").strip()))
-        import simplejson
-        values = simplejson.loads(cherrypy.response.body[0])
+        response = self.app.get("/flash_redirect?tg_format=json", status=302)
+        response = self.app.get(response.location,
+            headers=dict(Cookie=response.headers['Set-Cookie']))
+        values = simplejson.loads(response.body)
         assert values["tg_flash"] == u"redirect \xfcnicode"
 
     def test_flash_redirect_with_trouble_chars(self):
         """flash redirect with chars that can cause troubles in cookies"""
-        testutil.create_request("/flash_redirect_with_trouble_chars?tg_format=json")
-        assert cherrypy.response.status.startswith("302")
-        value = cherrypy.response.simple_cookie["tg_flash"].value
-        assert '$' not in value
-        assert ',' not in value and ';' not in value
-        assert ' ' not in value and '\t' not in value
-        assert 'foo' in value and 'bar' in value
-        assert u'k\xe4se'.encode('utf-8') in value
-        assert '!' in value
-        testutil.create_request(cherrypy.response.headers["Location"],
-            headers=dict(Cookie=cherrypy.response.simple_cookie.output(
-                header="").strip()))
-        import simplejson
-        values = simplejson.loads(cherrypy.response.body[0])
+        response = self.app.get("/flash_redirect_with_trouble_chars?tg_format=json", status=302)
+        response = self.app.get(response.location,
+                                headers=dict(Cookie=response.headers['Set-Cookie']))
+        values = simplejson.loads(response.body)
         assert values["tg_flash"] == u"$foo, k\xe4se;\tbar!"
 
     def test_double_flash(self):
@@ -505,30 +488,24 @@
         # Here we are calling method that sets a flash message. However flash
         # cookie is still there. Turbogears should discard old flash message
         # from cookie and use new one, set by flash_plain().
-        testutil.create_request("/flash_plain?tg_format=json",
+        response = self.app.get("/flash_plain?tg_format=json",
             headers=dict(Cookie='tg_flash="old flash"; Path=/;'))
-        import simplejson
-        values = simplejson.loads(cherrypy.response.body[0])
+        values = simplejson.loads(response.body)
         assert values["tg_flash"] == "plain"
-        assert cherrypy.response.simple_cookie.has_key("tg_flash"), \
+        assert response.cookies_set.has_key("tg_flash"), \
                 "Cookie clearing request should be present"
-        flashcookie = cherrypy.response.simple_cookie['tg_flash']
-        assert flashcookie['expires'] == 0
 
     def test_set_kid_outputformat_in_config(self):
         """the outputformat for kid can be set in the config"""
         config.update({'kid.outputformat': 'xhtml'})
-        testutil.create_request('/test')
-        response = cherrypy.response.body[0]
+        response = self.app.get('/test')
         assert '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ' in response
         config.update({'kid.outputformat': 'html'})
-        testutil.create_request('/test')
-        response = cherrypy.response.body[0]
+        response = self.app.get('/test')
         assert  '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML ' in response
         assert '    This is the groovy test ' in response
         config.update({'kid.outputformat': 'html compact'})
-        testutil.create_request('/test')
-        response = cherrypy.response.body[0]
+        response = self.app.get('/test')
         assert  '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML ' in response
         assert 'This is the groovy test ' in response
         assert '    ' not in response
@@ -536,33 +513,31 @@
     def test_fileserving(self):
         #outputcap = StringIO()
         #sys.stdout = outputcap
-        testutil.create_request("/servefile")
+        response = self.app.get("/servefile")
         assert cherrypy.root.servedit
         assert not cherrypy.root.serve_exceptions
         #assert "AssertionError" not in outputcap.getvalue()
 
     def test_internal_redirect(self):
         """regression test for #1022, #1407 and #1598"""
-        testutil.create_request("/internal_redirect")
-        firstline = cherrypy.response.body[0]
-        assert "redirected OK" in firstline
+        response = self.app.get("/internal_redirect")
+        assert "redirected OK" in response
 
     def test_internal_redirect_nested_variables(self):
         """regression test for #1022, #1407 and #1598"""
-        testutil.create_request(
+        response = self.app.get(
             "/internal_redirect?a=1&a-1.b=2&a-2.c=3&a-2.c-1=4")
-        firstline = cherrypy.response.body[0]
-        assert "redirected OK" in firstline
+        assert "redirected OK" in response
 
     def test_exc_value(self):
         """Exception is handled gracefully by the right exception handler."""
-        testutil.create_request("/raise_value_exc")
-        assert 'handling_value' in cherrypy.response.body[0]
+        response = self.app.get("/raise_value_exc")
+        assert 'handling_value' in response
 
     def test_exc_index(self):
         """Exception is handled gracefully by the right exception handler."""
-        testutil.create_request("/raise_index_exc")
-        assert 'handling_index' in cherrypy.response.body[0]
+        response = self.app.get("/raise_index_exc")
+        assert 'handling_index' in response
 
     def test_exc_all(self):
         """Test a controller that is protected by multiple exception handlers.
@@ -571,24 +546,25 @@
         by their respective handlers without problem...
 
         """
-        testutil.create_request("/raise_all_exc?num=1")
-        assert 'handling_value' in cherrypy.response.body[0]
-        testutil.create_request("/raise_all_exc?num=2")
-        assert 'handling_index' in cherrypy.response.body[0]
-        testutil.create_request("/raise_all_exc?num=3")
-        assert 'handling_key' in cherrypy.response.body[0]
+        response = self.app.get("/raise_all_exc?num=1")
+        assert 'handling_value' in response
+        response = self.app.get("/raise_all_exc?num=2")
+        assert 'handling_index' in response
+        response = self.app.get("/raise_all_exc?num=3")
+        assert 'handling_key' in response
 
 
-class TestURLs(unittest.TestCase):
+class TestURLs(testutil.TGWebTest):
 
     def setUp(self):
+        testutil.TGWebTest.setUp(self)
         cherrypy.tree.mount_points = {}
         cherrypy.root = MyRoot()
         cherrypy.root.subthing = SubApp()
         cherrypy.root.subthing.subsubthing = SubApp()
 
     def test_basic_urls(self):
-        testutil.create_request("/")
+        self.app.get("/")
         assert "/foo" == url("/foo")
         assert "foo/bar" == url(["foo", "bar"])
         assert url("/foo", bar=1, baz=2) in \
@@ -602,37 +578,30 @@
         assert url("/foo") == "/foo"
 
     def test_approots(self):
-        testutil.create_request("/subthing/")
+        config.update({"server.webpath": "/subthing"})
+        self.app.get("/subthing/")
         assert url("foo") == "foo"
         assert url("/foo") == "/subthing/foo"
 
     def test_lower_approots(self):
-        testutil.create_request("/subthing/subsubthing/")
+        config.update({"server.webpath": "/subthing/subsubthing"})
+        self.app.get("/subthing/subsubthing/")
         assert url("/foo") == "/subthing/subsubthing/foo"
 
-    def test_approots_With_path(self):
-        config.update({"server.webpath": "/coolsite/root"})
-        startup.startTurboGears()
-        testutil.create_request("/coolsite/root/subthing/")
+    def test_approots_with_path(self):
+        config.update({"server.webpath": "/coolsite/root/subthing"})
+        self.app.get("/subthing/")
         assert url("/foo") == "/coolsite/root/subthing/foo"
 
     def test_redirect(self):
         config.update({"server.webpath": "/coolsite/root"})
-        startup.startTurboGears()
-        testutil.create_request("/coolsite/root/subthing/")
-        try:
-            redirect("/foo")
-            assert False, "redirect exception should have been raised"
-        except cherrypy.HTTPRedirect, e:
-            assert "http://localhost/coolsite/root/subthing/foo" in e.urls
-        try:
-            raise redirect("/foo")
-            assert False, "redirect exception should have been raised"
-        except cherrypy.HTTPRedirect, e:
-            assert "http://localhost/coolsite/root/subthing/foo" in e.urls
+        response = self.app.get("/redirect")
+        assert response.location == 'http://localhost:80/coolsite/root/foo'
+        self.app.get("/raise_redirect")
+        assert response.location == 'http://localhost:80/coolsite/root/foo'
 
     def test_multi_values(self):
-        testutil.create_request("/")
+        self.app.get("/")
         assert url("/foo", bar=[1, 2]) in \
             ["/foo?bar=1&bar=2", "/foo?bar=2&bar=1"]
         assert url("/foo", bar=("asdf", "qwer")) in \
@@ -640,7 +609,7 @@
 
     def test_unicode(self):
         """url() can handle unicode parameters"""
-        testutil.create_request("/")
+        self.app.get("/")
         assert url('/', x=u'\N{LATIN SMALL LETTER A WITH GRAVE}'
             u'\N{LATIN SMALL LETTER E WITH GRAVE}'
             u'\N{LATIN SMALL LETTER I WITH GRAVE}'
@@ -650,45 +619,43 @@
 
     def test_list(self):
         """url() can handle list parameters, with unicode too"""
-        testutil.create_request("/")
+        self.app.get("/")
         assert url('/', foo=['bar', u'\N{LATIN SMALL LETTER A WITH GRAVE}']
             ) == '/?foo=bar&foo=%C3%A0'
 
     def tearDown(self):
+        testutil.TGWebTest.tearDown(self)
         config.update({"server.webpath": ""})
-        startup.startTurboGears()
 
 
-def test_index_trailing_slash():
-    """If there is no trailing slash on an index method call, redirect"""
-    cherrypy.root = SubApp()
-    cherrypy.root.foo = SubApp()
-    testutil.create_request("/foo")
-    assert cherrypy.response.status.startswith("302")
+    def test_index_trailing_slash(self):
+        """If there is no trailing slash on an index method call, redirect"""
+        cherrypy.root = SubApp()
+        cherrypy.root.foo = SubApp()
+        self.app.get("/foo", status=302)
 
-def test_can_use_internally_defined_arguments():
-    """Can use argument names that are internally used by TG in controllers"""
+    def test_can_use_internally_defined_arguments(self):
+        """Can use argument names that are internally used by TG in controllers"""
 
-    class App(controllers.RootController):
+        class App(controllers.RootController):
 
-        @expose()
-        def index(self, **kw):
-            return "\n".join(["%s:%s" % i for i in kw.iteritems()])
+            @expose()
+            def index(self, **kw):
+                return "\n".join(["%s:%s" % i for i in kw.iteritems()])
 
-    cherrypy.root = App()
-    testutil.create_request("/?format=foo&template=bar&fragment=boo")
-    output = cherrypy.response.body[0]
-    assert "format:foo" in output
-    assert "template:bar" in output
-    assert "fragment:boo" in output
+        cherrypy.root = App()
+        response = self.app.get("/?format=foo&template=bar&fragment=boo")
+        assert "format:foo" in response
+        assert "template:bar" in response
+        assert "fragment:boo" in response
 
-def test_url_kwargs_overwrite_tgparams():
-    """Check keys in tgparams in call to url overwrite kw args"""
-    params = {'spamm': 'eggs'}
-    assert 'spamm=ham' in url('/foo', params, spamm='ham')
+    def test_url_kwargs_overwrite_tgparams(self):
+        """Check keys in tgparams in call to url overwrite kw args"""
+        params = {'spamm': 'eggs'}
+        assert 'spamm=ham' in url('/foo', params, spamm='ham')
 
-def test_url_doesnt_change_tgparams():
-    """Test that url() does not change the dict passed as second arg"""
-    params = {'spamm': 'eggs'}
-    assert 'foo' in url('/foo', params, spamm='ham')
-    assert params['spamm'] == 'eggs'
+    def test_url_doesnt_change_tgparams(self):
+        """Test that url() does not change the dict passed as second arg"""
+        params = {'spamm': 'eggs'}
+        assert 'foo' in url('/foo', params, spamm='ham')
+        assert params['spamm'] == 'eggs'
Index: turbogears/tests/test_catwalk.py
===================================================================
--- turbogears/tests/test_catwalk.py	(revision 4618)
+++ turbogears/tests/test_catwalk.py	(working copy)
@@ -40,40 +40,36 @@
         pass
     index = turbogears.expose()(index)
 
-class Browse(unittest.TestCase):
+class Browse(testutil.TGWebTest):
     def setUp(self):
+        testutil.TGWebTest.setUp(self)
         browse_data(browse)
         cherrypy.root = MyRoot()
 
     def test_wrong_filter_format(self):
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Song&filters=Guantanemera&tg_format=json")
-        response = cherrypy.response.body[0]
+        response = self.app.get("/catwalk/browse/?object_name=Song&filters=Guantanemera&tg_format=json")
         assert 'filter_format_error' in response
 
     def test_wrong_filter_column(self):
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Song&filters=guacamole:2&tg_format=json")
-        response = cherrypy.response.body[0]
+        response = self.app.get("/catwalk/browse/?object_name=Song&filters=guacamole:2&tg_format=json")
         assert 'filter_column_error' in response
 
     def test_filters(self):
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Song&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Song&tg_format=json")
+        values = simplejson.loads(response.body)
         assert values['total'] == 15 * 15 * 15 #without the filters we get all songs (3375)
-        testutil.create_request("/catwalk/browse/?object_name=Song&filters=album:1&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Song&filters=album:1&tg_format=json")
+        values = simplejson.loads(response.body)
         assert values['total'] == 15 #filter by album id (only 15 songs)
 
     def test_response_fields(self):
         #Check that the response contains the expected keys
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Artist&start=3&page_size=20&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Artist&start=3&page_size=20&tg_format=json")
+        values = simplejson.loads(response.body)
         assert values.has_key('headers')
         assert values.has_key('rows')
         assert values.has_key('start')
@@ -87,9 +83,8 @@
         #Control that the count for related and multiple joins match
         #the number of related instances when accessed as a field
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Artist&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Artist&tg_format=json")
+        values = simplejson.loads(response.body)
         artist = browse.Artist.get(1)
         assert int(values['rows'][0]['genres']) == len(list(artist.genres))
         assert int(values['rows'][0]['albums']) == len(list(artist.albums))
@@ -97,50 +92,46 @@
     def test_rows_column_number(self):
         #Control that the number of columns match the number of fields in the model
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Artist&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Artist&tg_format=json")
+        values = simplejson.loads(response.body)
         assert len(values['rows'][0]) == 4
 
     def test_rows_limit(self):
         #Update the limit of rows for the query and control the number of rows returned
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Artist&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Artist&tg_format=json")
+        values = simplejson.loads(response.body)
         assert values.has_key('rows')
         assert len(values['rows']) == 10
-        testutil.create_request("/catwalk/browse/?object_name=Artist&page_size=15&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Artist&page_size=15&tg_format=json")
+        values = simplejson.loads(response.body)
         assert values.has_key('rows')
         assert len(values['rows']) == 15
 
     def test_header_labels(self):
         #Check that the returned header labels match the the model
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.create_request("/catwalk/browse/?object_name=Artist&tg_format=json")
-        response = cherrypy.response.body[0]
-        values = simplejson.loads(response)
+        response = self.app.get("/catwalk/browse/?object_name=Artist&tg_format=json")
+        values = simplejson.loads(response.body)
         assert len(values['headers']) == 5
         for header in values['headers']:
             assert header['name'] in ['id','name','albums','genres', 'plays_instruments']
 
 
-class TestJoinedOperations( testutil.DBTest):
+class TestJoinedOperations(testutil.DBWebTest):
     model = browse
 
     def setUp(self):
         cherrypy.root = MyRoot()
         cherrypy.root.catwalk = CatWalk(browse)
-        testutil.DBTest.setUp(self)
+        testutil.DBWebTest.setUp(self)
         browse_data(browse)
 
     def test_addremove_related_joins(self):
         # check the update_join function when nondefault add/remove are used
         artist = self.model.Artist.get(1)
         assert len(artist.plays_instruments) == 0
-        testutil.create_request("/catwalk/updateJoins?objectName=Artist&id=1&join=plays_instruments&joinType=&joinObjectName=Instrument&joins=1%2C2&tg_format=json")
+        response = self.app.get("/catwalk/updateJoins?objectName=Artist&id=1&join=plays_instruments&joinType=&joinObjectName=Instrument&joins=1%2C2&tg_format=json")
         assert len(artist.plays_instruments) == 2
-        testutil.create_request("/catwalk/updateJoins?objectName=Artist&id=1&join=plays_instruments&joinType=&joinObjectName=Instrument&joins=1&tg_format=json")
+        response = self.app.get("/catwalk/updateJoins?objectName=Artist&id=1&join=plays_instruments&joinType=&joinObjectName=Instrument&joins=1&tg_format=json")
         assert len(artist.plays_instruments) == 1, str(artist.plays_instruments)
Index: turbogears/tests/test_paginate.py
===================================================================
--- turbogears/tests/test_paginate.py	(revision 4618)
+++ turbogears/tests/test_paginate.py	(working copy)
@@ -4,11 +4,11 @@
 from urllib import quote
 import warnings
 
-from turbogears import config, expose, database
+from turbogears import config, expose, database, testutil
 from turbogears.controllers import RootController, url
 from turbogears.database import get_engine, metadata, session, mapper
 from turbogears.paginate import paginate, sort_ordering, sort_data
-from turbogears.testutil import create_request, sqlalchemy_cleanup
+from turbogears.testutil import sqlalchemy_cleanup
 from turbojson.jsonify import jsonify
 
 import cherrypy
@@ -183,7 +183,7 @@
     return result
 
 
-class TestSpy(unittest.TestCase):
+class TestSpy(testutil.TGWebTest):
     """Never trust a spy"""
 
     class MyRoot(RootController):
@@ -218,45 +218,42 @@
 
 
     def setUp(self):
+        testutil.TGWebTest.setUp(self)
         cherrypy.root = self.MyRoot()
 
     def test_spy(self):
-        create_request('/spy')
-        body = cherrypy.response.body[0]
-        Spy.assert_ok(body, 'current_page', 1)
+        response = self.app.get('/spy')
+        Spy.assert_ok(response.body, 'current_page', 1)
         try:
-            Spy.assert_ok(body, 'current_page', 2)
+            Spy.assert_ok(response.body, 'current_page', 2)
             raise Exception("above test should have failed")
         except AssertionError:
             pass
 
     def test_correct_expectation(self):
-        create_request('/spy_correct_expectation')
-        body = cherrypy.response.body[0]
-        assert "ok: [paginate" in body
+        response = self.app.get('/spy_correct_expectation')
+        assert "ok: [paginate" in response
 
     def test_wrong_expectation(self):
-        create_request('/spy_wrong_expectation')
-        body = cherrypy.response.body[0]
-        assert "fail: expected page_count=9, got page_count=10" in body
+        response = self.app.get('/spy_wrong_expectation')
+        assert "fail: expected page_count=9, got page_count=10" in response
 
     def test_invalid_expectation(self):
-        create_request('/spy_invalid_expectation')
-        body = cherrypy.response.body[0]
-        assert "fail: paginate does not have 'foobar' attribute" in body
+        response = self.app.get('/spy_invalid_expectation')
+        assert "fail: paginate does not have 'foobar' attribute" in response
 
     def test_raw_expectation(self):
-        create_request('/spy_correct_expectation')
-        Spy.assert_ok(cherrypy.response.body[0], 'var_name', 'data')
-        Spy.assert_ok(cherrypy.response.body[0], 'var_name', "'data'", raw=True)
+        response = self.app.get('/spy_correct_expectation')
+        Spy.assert_ok(response.body, 'var_name', 'data')
+        Spy.assert_ok(response.body, 'var_name', "'data'", raw=True)
 
 
-class TestPagination(unittest.TestCase):
+class TestPagination(testutil.TGWebTest):
     """Base class for all Paginate TestCases"""
 
-    def request(self, url):
-        create_request(url)
-        self.body = cherrypy.response.body[0]
+    def request(self, url, status=200):
+        response = self.app.get(url, status=status)
+        self.body = response.body
         if "fail: " in self.body:
             print self.body
             assert False, "Spy alert! Check body output for details..."
@@ -343,6 +340,7 @@
 
 
     def setUp(self):
+        testutil.TGWebTest.setUp(self)
         cherrypy.root = self.MyRoot()
 
     def test_pagination_old_style(self):
@@ -439,8 +437,7 @@
         Spy.assert_ok(self.body, 'pages', xrange(4, 8))
 
     def test_invalid_dynamic_limit(self):
-        self.request("/invalid_dynamic")
-        assert cherrypy.response.status.startswith("500")
+        self.request("/invalid_dynamic", status=500)
         assert 'paginate: dynamic_limit (foobar) not found in output dict' in self.body
 
     def test_dynamic_limit(self):
@@ -754,6 +751,7 @@
 
 
     def setUp(self):
+        testutil.TGWebTest.setUp(self)
         cherrypy.root = self.MyRoot()
 
     def assert_order(self, *args):
@@ -826,8 +824,7 @@
 
     def test_invalid_default_reversed(self):
         for method in "Q", "QA", "SR", "SO", "SL":
-            self.request("/wrong_reversed/?method=%s" % method)
-            assert cherrypy.response.status.startswith("500")
+            self.request("/wrong_reversed/?method=%s" % method, status=500)
             assert ('paginate: default_reversed (deprecated) only allowed'
                 ' when default_order is a basestring') in self.body
 
Index: turbogears/testutil.py
===================================================================
--- turbogears/testutil.py	(revision 4618)
+++ turbogears/testutil.py	(working copy)
@@ -18,6 +18,8 @@
 except ImportError:
     sqlalchemy = None
 
+from webtest import TestApp
+
 from turbogears import startup, config, update_config, \
     controllers, database, validators
 from turbogears.identity import current_provider
@@ -201,6 +203,45 @@
                 item.dropTable(ifExists=True)
 
 
+class TGWebTest(unittest.TestCase):
+    """A WebTest enabled unit testing class.
+
+    This allows testers to subclass us and use self.app to make WebTest calls.
+    """
+    def setUp(self):
+        from cherrypy._cpwsgi import wsgiApp
+        start_cp()
+        startup.startTurboGears()
+        self.app = TestApp(wsgiApp)
+
+    def tearDown(self):
+        startup.stopTurboGears()
+        del self.app
+
+    def login_user(self, user):
+        """ Log a specified user object into the system """
+        self.app.post(config.get('identity.failure_url'), {
+                'user_name' : user.user_name,
+                'password'  : user.password,
+                'login'     : 'Login',
+        })
+
+
+class DBWebTest(TGWebTest, DBTest):
+    """A database driven WebTest enabled unit testing class.
+
+    This class will automatically setup and tear down your database, along
+    with TurboGears and WebTest before and after each unit test.
+    """
+    def setUp(self):
+        DBTest.setUp(self)
+        TGWebTest.setUp(self)
+
+    def tearDown(self):
+        TGWebTest.tearDown(self)
+        DBTest.tearDown(self)
+
+
 def reset_cp():
     cherrypy.root = None
 
@@ -307,4 +348,5 @@
 
 __all__ = ["call", "create_request", "DBTest",
     "attach_identity", "set_identity_user",
-    "capture_log", "print_log", "get_log", "sqlalchemy_cleanup"]
+    "capture_log", "print_log", "get_log", "sqlalchemy_cleanup",
+    "TGWebTest", "DBWebTest"]
Index: turbogears/view/base.py
===================================================================
--- turbogears/view/base.py	(revision 4618)
+++ turbogears/view/base.py	(working copy)
@@ -107,6 +107,9 @@
     @type template: string
 
     """
+    if hasattr(cherrypy.request, 'wsgi_environ'):
+        if 'paste.testing' in cherrypy.request.wsgi_environ:
+            cherrypy.request.wsgi_environ['paste.testing_variables']['namespace'] = info
     template = info.pop("tg_template", template)
     if not info.has_key("tg_flash"):
         if config.get("tg.empty_flash", True):

