Index: setup.py
===================================================================
--- setup.py	(revision 4621)
+++ setup.py	(working copy)
@@ -46,10 +46,12 @@
 
 testtools =  [
     "nose >= 0.9.3, <= 0.10.0a1",
+    "WebTest",
 ]
 
 tgtesttools =  [
     "nose >= 0.9.3, <= 0.10.0a1",
+    "WebTest",
 ]
 
 # python 2.5 compatible list
Index: turbogears/identity/tests/test_identity.py
===================================================================
--- turbogears/identity/tests/test_identity.py	(revision 4621)
+++ turbogears/identity/tests/test_identity.py	(working copy)
@@ -55,7 +55,7 @@
 
     @expose()
     def index(self):
-        pass
+        return 'identityroot index'
 
     @expose()
     def identity_failed(self, **kw):
@@ -69,9 +69,35 @@
     @expose()
     @identity.require(in_group('peon'))
     def in_peon_group(self):
+        user = TG_User.by_user_name("samIam") 
+        group_ids = set((TG_Group.by_group_name("peon").id,
+            TG_Group.by_group_name("other").id))
+        assert identity.current.user == user
+        assert identity.current.user_name == user.user_name
+        assert identity.current.user_id == user.id
+        assert identity.current.groups == set(('peon', 'other'))
+        assert identity.current.group_ids == group_ids
+        assert "samIam" == cherrypy.serving.request.identity.user_name
+
         return 'in_peon_group'
 
     @expose()
+    @identity.require(in_group('peon'))
+    def identity_current_set(self):
+        user = TG_User.by_user_name("samIam")
+        group_ids = set((TG_Group.by_group_name("peon").id,
+            TG_Group.by_group_name("other").id))
+        assert identity.current.user == user
+        assert identity.current.user_name == user.user_name
+        assert identity.current.user_id == user.id
+        assert identity.current.groups == set(('peon', 'other'))
+        assert identity.current.group_ids == group_ids
+        assert "samIam" == cherrypy.serving.request.identity.user_name
+
+        return 'identity_current_set'
+
+
+    @expose()
     def test_exposed_require(self):
         if not hasattr(self.in_peon_group, '_require'):
             return 'no _require attr'
@@ -136,6 +162,11 @@
             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
+        return 'is_anonymous'
 
 class TestIdentity(unittest.TestCase):
 
@@ -151,12 +182,11 @@
         self._original_config = original_config
         config.configure_loggers(test_config)
         config.update(test_config['global'])
-        cherrypy.root = IdentityRoot()
-        startup.startTurboGears()
+        testutil.start_server(root = IdentityRoot())
         self.init_model()
 
     def tearDown(self):
-        startup.stopTurboGears()
+        testutil.stop_server()
         config.update(self._original_config)
 
     def init_model(self):
@@ -181,9 +211,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 = testutil.go('/new_user_setup?user_name=test&password=pw')
+        assert 'test pw' in response, response
 
     def test_user_exists(self):
         u = TG_User.by_user_name('samIam')
@@ -203,9 +232,6 @@
         """Test if we can set a user password which is encoded as unicode
         (no encryption algorithm)."""
         config.update({'identity.soprovider.encryption_algorithm': None})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'
@@ -217,9 +243,6 @@
     def test_user_password_hashed_sha(self):
         """Test if a sha hashed password is stored in the database."""
         config.update({'identity.soprovider.encryption_algorithm': 'sha1'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = 'password'
@@ -232,9 +255,6 @@
         """Test if a sha hashed password with unicode characters is stored
         in the database."""
         config.update({'identity.soprovider.encryption_algorithm': 'sha1'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'
@@ -246,9 +266,6 @@
     def test_user_password_hashed_md5(self):
         """Test if a md5 hashed password is stored in the database."""
         config.update({'identity.soprovider.encryption_algorithm': 'md5'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = 'password'
@@ -261,9 +278,6 @@
         """Test if a md5 hashed password with unicode characters is stored
         in the database."""
         config.update({'identity.soprovider.encryption_algorithm': 'md5'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'
@@ -278,9 +292,6 @@
         test ensures that the encryption algorithm does handle non-unicode
         parameters gracefully."""
         config.update({'identity.soprovider.encryption_algorithm': 'md5'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = u'garçon'.encode('UTF-8')
@@ -293,9 +304,6 @@
         """Test that we can store raw values in the password field
         (without being hashed)."""
         config.update({'identity.soprovider.encryption_algorithm':'sha1'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.set_password_raw('password')
@@ -306,9 +314,6 @@
 
     def test_user_password_raw_unicode(self):
         config.update({'identity.soprovider.encryption_algorithm':'sha1'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.set_password_raw(u'garçon')
@@ -322,9 +327,6 @@
         config.update({'identity.soprovider.encryption_algorithm': 'custom',
             'identity.custom_encryption':
                 'identity.tests.test_identity.mycustomencrypt'})
-        # force new config values to load
-        startup.startTurboGears()
-        testutil.create_request('/')
         hub.begin()
         u = TG_User.by_user_name('samIam')
         u.password = 'password'
@@ -335,175 +337,135 @@
 
     def test_anonymous_browsing(self):
         """Test if we can show up anonymously."""
-        testutil.create_request('/')
-        assert identity.current.anonymous
+        response = testutil.go('/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 = testutil.go('/logged_in_only')
+        assert 'identity_failed_answer' in response.body
 
     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 = testutil.go('/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.body
 
     def test_logout(self):
-        """Test that logout works and session is gets invalid afterwards."""
-        testutil.create_request('/in_peon_group?'
+        """Test that logout works and session is invalid afterwards."""
+        response = testutil.go('/identity_current_set?'
             '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 = testutil.go('/logout', headers={'Cookie': session_id})
+        response = testutil.go('/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
-        (e.g. when testutils.set_identity_user is used)."""
-        request = testutil.DummyRequest()
-        old_user = testutil.test_user
-        user = TG_User.by_user_name("samIam")
-        testutil.set_identity_user(user)
-        testutil.attach_identity(request)
-        testutil.set_identity_user(old_user)
-        testutil.call_with_request(cherrypy.root.logout, request)
-        assert request.identity.anonymous
-
     def test_require_group(self):
         """Test that a anonymous user"""
-        testutil.create_request('/in_peon_group')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        response = testutil.go('/in_peon_group')
+        assert 'identity_failed_answer' in response.body
 
     def test_require_expose_required_permission(self):
         """Test that the decorator exposes the correct permissions via _require
         attribute on the actual method."""
-        testutil.create_request('/test_exposed_require')
-        firstline= cherrypy.response.body[0]
-        assert 'require is exposed' in firstline, firstline
+        response = testutil.go('/test_exposed_require')
+        assert 'require is exposed' in response.body
 
     def test_require_group_viewable(self):
         """Test that a user with proper group membership can see a restricted url."""
-        testutil.create_request('/in_peon_group?'
+        response = testutil.go('/in_peon_group?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'in_peon_group' in firstline, firstline
-        user = TG_User.by_user_name("samIam")
+        assert 'in_peon_group' in response.body
 
     def test_require_group_viewable(self):
         """Test that the current user and group properties are set correctly."""
-        testutil.create_request('/in_peon_group?'
+        response = testutil.go('/identity_current_set?'
             'user_name=samIam&password=secret&login=Login')
-        user = TG_User.by_user_name("samIam")
-        group_ids = set((TG_Group.by_group_name("peon").id,
-            TG_Group.by_group_name("other").id))
-        assert identity.current.user == user
-        assert identity.current.user_name == user.user_name
-        assert identity.current.user_id == user.id
-        assert identity.current.groups == set(('peon', 'other'))
-        assert identity.current.group_ids == group_ids
+        assert response.body == 'identity_current_set'
 
     def test_user_not_in_right_group(self):
         """Test that a user is denied access if they aren't in the right group."""
-        testutil.create_request('/in_admin_group?'
+        response = testutil.go('/in_admin_group?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        assert 'identity_failed_answer' in response.body
 
     def test_require_permission(self):
         """Test that an anonymous user is denied access to a permission restricted url."""
-        testutil.create_request('/has_chopper_permission')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        response = testutil.go('/has_chopper_permission')
+        assert 'identity_failed_answer' in response.body
 
     def test_require_permission_viewable(self):
         """Test that a user with proper permissions can see a restricted url."""
-        testutil.create_request('/has_chopper_permission?'
+        response = testutil.go('/has_chopper_permission?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'has_chopper_permission' in firstline, firstline
+        assert 'has_chopper_permission' in response.body
 
     def test_user_lacks_permission(self):
         """Test that a user is denied acces if they don't have the proper permission."""
-        testutil.create_request('/has_boss_permission?'
+        response = testutil.go('/has_boss_permission?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        assert 'identity_failed_answer' in response.body
 
     def test_user_info_available(self):
         """Test that we can see user information inside our controller."""
-        testutil.create_request('/user_email?'
+        response = testutil.go('/user_email?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'samiam@example.com' in firstline, firstline
+        assert 'samiam@example.com' in response.body
 
     def test_bad_login(self):
         """Test that we are denied access if we provide a bad login."""
-        testutil.create_request('/logged_in_only?'
+        response = testutil.go('/logged_in_only?'
             'user_name=samIam&password=wrong&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        assert 'identity_failed_answer' in response.body
 
     def test_restricted_subdirectory(self):
         """Test that we can restrict access to a whole subdirectory."""
-        testutil.create_request('/peon_area/index')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        response = testutil.go('/peon_area/index')
+        assert 'identity_failed_answer' in response.body
 
     def test_restricted_subdirectory_viewable(self):
         """Test that we can access a restricted subdirectory
         if we have proper credentials."""
-        testutil.create_request('/peon_area/index?'
+        response = testutil.go('/peon_area/index?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'restricted_index' in firstline, firstline
+        assert 'restricted_index' in response.body
 
     def test_decoratator_in_restricted_subdirectory(self):
         """Test that we can require a different permission
         in a protected subdirectory."""
-        testutil.create_request('/peon_area/in_other_group?'
+        response = testutil.go('/peon_area/in_other_group?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'in_other_group' in firstline, firstline
+        assert 'in_other_group' in response.body
 
     def test_decoratator_failure_in_restricted_subdirectory(self):
         """Test that we can get an identity failure from a decorator
         in a restricted subdirectory"""
-        testutil.create_request('/peon_area/in_admin_group?'
+        response = testutil.go('/peon_area/in_admin_group?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        assert 'identity_failed_answer' in response.body
 
     def test_explicit_checks_in_restricted_subdirectory(self):
         """Test that explicit permission checks in a protected
         directory is handled as expected"""
-        testutil.create_request('/peon_area/in_other_group_explicit_check?'
+        response = testutil.go('/peon_area/in_other_group_explicit_check?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'in_other_group' in firstline, firstline
+        assert 'in_other_group' in response.body
 
     def test_throwing_identity_exception_in_restricted_subdirectory(self):
         """Test that throwing an IdentityException in a protected
         directory is handled as expected"""
-        testutil.create_request('/peon_area/in_admin_group_explicit_check?'
+        response = testutil.go('/peon_area/in_admin_group_explicit_check?'
             'user_name=samIam&password=secret&login=Login')
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed' in firstline, firstline
+        assert 'identity_failed' in response.body
 
     def test_decode_filter_whenidfails(self):
         """Test that the decode filter doesn't break with nested
         variables and Identity"""
         params = urllib.quote(IdentityRoot._test_encoded_params.decode(
             'utf-8').encode('latin-1'), '=&')
-        testutil.create_request('/test_params?' + params)
-        firstline = cherrypy.response.body[0]
-        assert 'identity_failed_answer' in firstline, firstline
+        response = testutil.go('/test_params?' + params)
+        assert 'identity_failed_answer' in response.body
 
     def test_decode_filter_whenidworks(self):
         """Test that the decode filter doesn't break with nested
@@ -511,9 +473,8 @@
         params = urllib.quote(IdentityRoot._test_encoded_params.decode(
             'utf-8').encode('latin-1'), '=&')
         params += '&user_name=samIam&password=secret&login=Login'
-        testutil.create_request('/test_params?' + params)
-        firstline = cherrypy.response.body[0]
-        assert 'params ok' in firstline, firstline
+        response = testutil.go('/test_params?' + params)
+        assert 'params ok' in response.body
 
 
 class TestTGUser(testutil.DBTest):
@@ -522,18 +483,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.start_server()
         testutil.DBTest.setUp(self)
 
     def tearDown(self):
         testutil.DBTest.tearDown(self)
-        startup.stopTurboGears()
-        cherrypy.request.identityProvider = self._provider
+        testutil.stop_server()
         config.update({'identity.on': self._identity_on})
 
     def test_create_user(self):
Index: turbogears/identity/tests/test_visit.py
===================================================================
--- turbogears/identity/tests/test_visit.py	(revision 4621)
+++ turbogears/identity/tests/test_visit.py	(working copy)
@@ -2,20 +2,24 @@
 from unittest import TestCase
 import cherrypy
 from turbogears import config, controllers, expose, startup, testutil, visit
+from Cookie import SimpleCookie
 
 
-def cookie_header(morsel):
+def cookie_header(response):
     """Returns a dict containing cookie information to pass to a server."""
-    return {'Cookie': morsel.output(header="")[1:]}
+    return dict(Cookie=response.headers['Set-Cookie'])
 
 
 class VisitRoot(controllers.RootController):
 
     @expose()
     def index(self):
-        return dict()
+        new = None
+        if visit.current():
+            new = visit.current().is_new
+        return dict(new=new)
+    
 
-
 class TestVisit(TestCase):
 
     def setUp(self):
@@ -27,27 +31,27 @@
         cherrypy.root = VisitRoot()
 
     def tearDown(self):
-        startup.stopTurboGears()
+        testutil.stop_server()
         config.update({'visit.timeout': self._visit_timeout})
         config.update({'visit.on': self._visit_on})
 
     def test_visit_response(self):
         """Test if the visit cookie is set in cherrypy.response."""
-        testutil.create_request("/")
-        assert cherrypy.response.simple_cookie.has_key(self.cookie_name)
+        response = testutil.go("/")
+        assert response.cookies_set.has_key(self.cookie_name)
 
     def test_new_visit(self):
         """Test that we can see a new visit on the server."""
-        testutil.create_request("/")
-        assert visit.current().is_new
+        response = testutil.go("/")
+        assert response.raw['new']
 
     def test_old_visit(self):
         """Test if we can track a visitor over time."""
-        testutil.create_request("/")
+        response = testutil.go("/")
         # first visit's cookie
-        morsel = cherrypy.response.simple_cookie[self.cookie_name]
-        testutil.create_request("/", headers=cookie_header(morsel))
-        assert not visit.current().is_new
+        morsel = response.cookies_set[self.cookie_name]
+        response = testutil.go("/", headers=cookie_header(response))
+        assert not response.raw['new']
 
     def test_cookie_expires(self):
         """Test if the visit timeout mechanism works."""
@@ -55,22 +59,23 @@
         try:
             # set expiration to one second for this test only
             config.update({'visit.timeout': 1.0/60})
-            testutil.create_request("/")
-            morsel = cherrypy.response.simple_cookie[self.cookie_name]
+            response = testutil.go("/")
+            morsel = response.cookies_set[self.cookie_name]
             time.sleep(2) # 2 seconds
-            testutil.create_request("/", headers=cookie_header(morsel))
+            response = testutil.go("/", headers=cookie_header(response))
         finally:
             config.update({'visit.timeout': timeout})
-        assert cherrypy.response.simple_cookie[
-                self.cookie_name].value != morsel.value, \
+        assert response.cookies_set[
+                self.cookie_name] != morsel, \
             'cookie values should not match'
-        assert visit.current().is_new, \
+        assert response.raw['new'], \
             'this should be a new visit, as the cookie has expired'
 
     def test_cookie_not_permanent(self):
         """Check that by default the visit cookie is not permanent."""
-        testutil.create_request('/')
-        morsel = cherrypy.response.simple_cookie[self.cookie_name]
+        response = testutil.go('/')
+        cookies = SimpleCookie(response.headers['Set-Cookie'])
+        morsel = cookies[self.cookie_name]
         assert not morsel['expires'] and not morsel['max-age']
 
     def test_cookie_permanent(self):
@@ -80,13 +85,15 @@
             # set cookie permanent for this test only (needs restart)
             startup.stopTurboGears()
             config.update({'visit.cookie.permanent': True})
-            startup.startTurboGears()
-            testutil.create_request('/')
-            morsel = cherrypy.response.simple_cookie[self.cookie_name]
+            testutil.start_server()
+            response = testutil.go('/')
+            cookies = SimpleCookie(response.headers['Set-Cookie'])
+            morsel = cookies[self.cookie_name]
         finally:
             config.update({'visit.cookie.permanent': permanent})
-        assert morsel['max-age'] == 3000
+        assert morsel['max-age'] == '3000'
         expires = time.mktime(time.strptime(morsel['expires'],
             '%a, %d-%b-%Y %H:%M:%S GMT')[:8] + (0,))
-        should_expire = time.mktime(time.gmtime()) + morsel['max-age']
-        assert abs(should_expire - expires) < 3
+        should_expire = time.mktime(time.gmtime()) + int(morsel['max-age'])
+        assert abs(should_expire - expires) < 3, (should_expire, expires, should_expire - expires)
+
Index: turbogears/visit/api.py
===================================================================
--- turbogears/visit/api.py	(revision 4621)
+++ turbogears/visit/api.py	(working copy)
@@ -251,7 +251,7 @@
         max_age = self.cookie_max_age
         if max_age:
             # use 'expires' because MSIE ignores 'max-age'
-            cookies[self.cookie_name]['expires'] = time.strftime(
+            cookies[self.cookie_name]['expires'] = '"%s"' % time.strftime(
                 "%a, %d-%b-%Y %H:%M:%S GMT",
                 time.gmtime(time.time() + max_age))
             # 'max-age' takes precedence on standard conformant browsers
Index: turbogears/tests/test_form_controllers.py
===================================================================
--- turbogears/tests/test_form_controllers.py	(revision 4621)
+++ turbogears/tests/test_form_controllers.py	(working copy)
@@ -31,90 +31,82 @@
     def testform(self, name, date, age, tg_errors=None):
         if tg_errors:
             self.has_errors = True
-        self.name = name
-        self.age = age
-        self.date = date
+        return dict(name=name, user_age=age, birthdate=date)
 
     @expose()
     @validate(form=myform)
     def testform_new_style(self, name, date, age):
         if cherrypy.request.validation_errors:
             self.has_errors = True
-        self.name = name
-        self.age = age
-        self.date = date
+        return dict(name=name, age=age, date=date)
 
 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
+    testutil.start_server(root = MyRoot())
+    response = testutil.go("/testform?name=ed&date=11/05/2005&age=5")
+    assert response.raw['name'] == "ed"
+    assert response.raw['user_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
+    testutil.start_server(root = MyRoot())
+    response = testutil.go("/testform_new_style?name=ed&date=11/05/2005&age=5&")
+    assert response.raw['name'] == "ed"
+    assert response.raw['age'] == 5
 
 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
+    response = testutil.go("/testform?name=ed&age=edalso&date=11/05/2005")
 
 def test_css_should_appear():
     """CSS should appear when asked for"""
-    testutil.create_request("/")
-    assert "calendar-system.css" in cherrypy.response.body[0]
+    testutil.start_server(root = MyRoot())
+    response = testutil.go("/")
+    assert "calendar-system.css" in response
 
 def test_javascript_should_appear():
     """JavaScript should appear when asked for"""
-    testutil.create_request("/")
-    assert "calendar.js" in cherrypy.response.body[0]
+    response = testutil.go("/")
+    assert "calendar.js" in response
 
 def test_include_mochikit():
     """JSLinks (and MochiKit especially) can be included easily"""
-    testutil.create_request("/usemochi")
-    assert "MochiKit.js" in cherrypy.response.body[0]
+    response = testutil.go("/usemochi")
+    assert "MochiKit.js" 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]
+    config.update({"global":{"tg.mochikit_suppress" : True}})
+    suppressed = testutil.go("/usemochi")
     # 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
+    config.update({"global":{"tg.mochikit_suppress" : False}})
 
+    included = testutil.go("/usemochi")
+    assert "MochiKit.js" not in suppressed.body
+    assert "MochiKit.js" in included.body
+
 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]
+    config.update({"global":{"tg.mochikit_all" : True}})
+    response = testutil.go("/")
+    config.update({"global":{"tg.mochikit_all" : False}})
+    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("/")
+    response = testutil.go("/")
     config.update({"global": {"tg.mochikit_all": False}})
     config.update({"global": {"tg.mochikit_suppress": False}})
-    assert "MochiKit.js" not in cherrypy.response.body[0]
+    assert "MochiKit.js" not in response
 
 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("/")
+    response = testutil.go("/")
     config.update({"global": {"tg.include_widgets": []}})
-    assert "MochiKit.js" in cherrypy.response.body[0]
+    assert "MochiKit.js" in response
 
 
 class State(object):
@@ -146,11 +138,10 @@
         super(TestValidationState, self).__init__(*args, **kw)
 
     def test_counter_is_incremented(self):
-        cherrypy.root = self.Controller()
+        testutil.start_server(root = self.Controller())
         # parameter values are irrelevant
         url = '/validation?a=1&b=2&c.a=3&c.b=4'
-        testutil.create_request(url)
-        body = cherrypy.response.body[0]
+        response = testutil.go(url)
         msg = "Validation state is not handled properly"
         # 4 == 1 (a) + 1(b) + 1(c.a) + 1(c.b)
-        self.failUnless('counter: 4' in body, msg)
+        self.failUnless('counter: 4' in response, msg)
Index: turbogears/tests/test_errorhandling.py
===================================================================
--- turbogears/tests/test_errorhandling.py	(revision 4621)
+++ turbogears/tests/test_errorhandling.py	(working copy)
@@ -1,7 +1,4 @@
 import unittest
-
-import cherrypy
-
 from turbogears.controllers import error_handler, exception_handler, \
                                    expose, validate, RootController, Controller
 from turbogears.errorhandling import FailsafeSchema
@@ -94,11 +91,8 @@
         "second": validators.Int(not_empty=True)})
     @error_handler(defaulterrorhandler)
     def positionalargs(self, first, second, *args, **kw):
-        self.first = first
-        self.second = second
-        self.third = args[0]
         return dict(title="Positional arguments", first=first, second=second,
-                    args=args, bar=kw["bar"])
+                    third=args[0], args=args, bar=kw["bar"])
 
     @expose()
     @validate(validators={"bar": validators.Int(not_empty=True)})
@@ -141,8 +135,9 @@
         return self.notexposed(bar)
 
     def continuation(self, tg_source):
-        self.continuation = True
-        return tg_source(self)
+        response = tg_source(self)
+        response['continuation'] = True
+        return response
 
     @expose()
     @validate(validators={"bar": validators.Int(not_empty=True)})
@@ -211,130 +206,124 @@
 class TestErrorHandler(unittest.TestCase):
 
     def setUp(self):
-        cherrypy.root = MyRoot()
-        cherrypy.root.nestedcontroller = NestedController()
+        testutil.mount(MyRoot())
+        testutil.mount(NestedController(), "/nestedcontroller")
+        testutil.start_server()
 
     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 = testutil.go("/defaulterror?bar=abc")
+        self.failUnless("Default error handler" in response)
+        response = testutil.go("/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")
-        self.failUnless("Specialised error handler" in
-                        cherrypy.response.body[0])
-        testutil.create_request("/specialisederror?bar=1&baz=a@b.com")
-        self.failUnless("Specialised error provider" in
-                        cherrypy.response.body[0])
+        response = testutil.go("/specialisederror?bar=abc&baz=a@b.com")
+        self.failUnless("Default error handler" in response)
+        response = testutil.go("/specialisederror?baz=abc&bar=1")
+        self.failUnless("Specialised error handler" in response)
+        response = testutil.go("/specialisederror?bar=1&baz=a@b.com")
+        self.failUnless("Specialised error provider" in response)
 
     def test_exceptionErrorHandler(self):
         """Error handler for exceptions."""
-        testutil.create_request("/exceptionerror")
-        self.failUnless("Default error handler" in cherrypy.response.body[0])
+        response = testutil.go("/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")
-        self.failUnless("Recursive error provider" in
-                        cherrypy.response.body[0])
+        response = testutil.go("/recursiveerror?bar=abc")
+        self.failUnless("Recursive error handler" in response)
+        response = testutil.go("/recursiveerror?bar=1")
+        self.failUnless("Recursive error provider" in response)
 
     def test_implicitErrorHandler(self):
         """Implicit error handling."""
-        testutil.create_request("/impliciterror?bar=abc")
-        self.failUnless("Implicit error handler" in
-                        cherrypy.response.body[0])
-        testutil.create_request("/impliciterror?bar=1")
-        self.failUnless("Implicit error provider" in
-                        cherrypy.response.body[0])
+        response = testutil.go("/impliciterror?bar=abc")
+        self.failUnless("Implicit error handler" in response)
+        response = testutil.go("/impliciterror?bar=1")
+        self.failUnless("Implicit error provider" in 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 = testutil.go("/normalmethodcaller?bar=abc")
+        self.failUnless("Normal method" in response)
+        response = testutil.go("/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 = testutil.go("/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])
-        self.failUnless(cherrypy.root.first == "first")
-        self.failUnless(cherrypy.root.second == 23)
-        self.failUnless(cherrypy.root.third == "third")
+        response = testutil.go("/positionalargs/first/23/third?bar=abc")
+        self.failUnless("Default error handler" in response)
+        response = testutil.go("/positionalargs/first/abc/third?bar=false")
+        self.failUnless("Default error handler" in response)
+        response = testutil.go("/positionalargs/first/abc/third?bar=abc")
+        self.failUnless("Default error handler" in response)
+        response = testutil.go("/positionalargs/first/23/third?bar=true")
+        self.failUnless("Positional arguments" in response)
+        self.failUnless(response.raw['first'] == "first")
+        self.failUnless(response.raw['second'] == 23)
+        self.failUnless(response.raw['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 = testutil.go("/missingargs")
+        self.failUnless("Default error handler" in response)
+        response = testutil.go("/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 = testutil.go("/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 = testutil.go("/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 = testutil.go("/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])
-        self.failUnless(cherrypy.root.continuation == True)
+        response = testutil.go("/continuationcaller?bar=a")
+        self.failUnless("Continuation caller" in response)
+        self.failUnless(response.raw['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 = testutil.go("/nest?bar=a")
+        self.failUnless("Default error handler" in response)
+        response = testutil.go("/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")
-        self.failUnless('"bar": "Please enter an integer value"' in
-                        cherrypy.response.body[0])
-        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 = testutil.go("/failsafenone?bar=a&baz=b")
+        self.failUnless('"bar": "a"' in response)
+        self.failUnless('"baz": "b"' in response)
+        response = testutil.go("/failsafevaluesdict?bar=a&baz=b")
+        self.failUnless('"bar": 1' in response)
+        self.failUnless('"baz": 2' in response)
+        response = testutil.go("/failsafevaluesatom?bar=a&baz=b")
+        self.failUnless('"bar": 13' in response)
+        self.failUnless('"baz": 13' in response)
+        response = testutil.go("/failsafemaperrors?bar=a&baz=b")
+        self.failUnless('"bar": "Please enter an integer value"' in response)
+        self.failUnless('"baz": "Please enter an integer value"' in response)
+        response = testutil.go("/failsafeformencode?bar=a&baz=b")
+        self.failUnless('"bar": 1' in response)
+        self.failUnless('"baz": 2' in response)
+        response = testutil.go("/failsafedefaults?bar=a&baz=b")
+        self.failUnless('"bar": 1' in response)
+        self.failUnless('"baz": 2' in response)
Index: turbogears/tests/test_sqlalchemy.py
===================================================================
--- turbogears/tests/test_sqlalchemy.py	(revision 4621)
+++ turbogears/tests/test_sqlalchemy.py	(working copy)
@@ -9,7 +9,8 @@
 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 import testutil
+from turbogears.testutil import go, sqlalchemy_cleanup, \
     capture_log, print_log
 
 
@@ -135,7 +136,6 @@
         will be marked with this name :)
 
         """
-        cherrypy.response.code = 501
         msg = "KARL25 responding\n"
         msg += "user with id: '%s' should not be saved.\n" % id
         msg += "An exception occurred: %r (%s)" % ((tg_exceptions,)*2)
@@ -165,14 +165,12 @@
 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
+    testutil.start_server(root = MyRoot())
+    response = go("/no_error?name=A.%20Dent")
     print_log()
     q = session.query(Person)
     arthur = q.filter_by(name="A. Dent").first()
-    assert 'someconfirmhandler' in output, \
+    assert 'someconfirmhandler' in response, \
         '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"'
@@ -180,31 +178,23 @@
 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]
+    testutil.start_server(root = MyRoot())
+    response = go("/create_person?id=20")
     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, \
+    assert 'No exceptions occurred' in response
+    response = go("/create_person?id=20", status=500)
+    assert 'KARL25' not in response, \
         'Exception should NOT have been handled by our handler'
-    assert 'DBAPIError' in output, \
+    assert 'DBAPIError' in response, \
         'The page should have displayed an SQLAlchemy exception'
 
 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]
+    testutil.start_server(MyRoot())
+    response = go("/create_person?id=19&docom=0&doerr=1&name=Martin%20GAL")
     print_log()
-    print output
-    assert 'KARL25' in output, \
+    assert 'KARL25' in response, \
         'The exception handler should have answered us'
     p = session.query(Person).get(19)
     assert p is None, \
@@ -212,43 +202,39 @@
 
 def test_user_redirect():
     """If a controller redirects, transactions are committed."""
-    cherrypy.root = MyRoot()
-    create_request("/create_person?id=22&doerr=2")
+    testutil.start_server(MyRoot())
+    go("/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_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]
+    testutil.start_server(MyRoot())
+    response = go("/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_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]
+    testutil.start_server(MyRoot())
+    response = go("/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():
     """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]
+    testutil.start_server(MyRoot())
+    response = go("/create_person?id=25&doflush=1")
+    assert 'No exceptions occurred' in response
+    response = go("/create_person?id=25&doflush=0", status=500)
+    assert 'IntegrityError' in response
+    response = go("/create_person?id=25&doflush=1")
+    assert 'IntegrityError' in response
+    response = go("/create_person?id=25&doflush=2")
+    assert 'No exceptions occurred' in response
 
 
 # Exception handling with rollback
@@ -301,11 +287,9 @@
     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'
+    testutil.start_server(RbRoot())
+    response = go('/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
 
@@ -316,18 +300,16 @@
     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'
+    testutil.start_server(RbRoot())
+    response = go('/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'
+    testutil.start_server(RbRoot())
+    response = go('/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
 
@@ -360,19 +342,16 @@
 
     """
     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]
+    testutil.start_server(FreshRoot())
+    response = go("/test1", status = 200)
+    assert 'AssertionError' not in response
     # 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]
+            response = go("/test2", status=200)
+            assert 'AssertionError' not in response
     thrdb = ThreadB()
     thrdb.start()
     thrdb.join()
-    create_request("/test3")
-    assert cherrypy.response.status.startswith("200")
-    assert 'AssertionError' not in cherrypy.response.body[0]
+    response = go("/test3", status=200)
+    assert 'AssertionError' not in response
Index: turbogears/tests/test_expose.py
===================================================================
--- turbogears/tests/test_expose.py	(revision 4621)
+++ turbogears/tests/test_expose.py	(working copy)
@@ -1,8 +1,6 @@
-import cherrypy
 import simplejson
-
 from turbogears import controllers, expose
-from turbogears.testutil import create_request
+from turbogears.testutil import go, start_server
 
 
 class ExposeRoot(controllers.RootController):
@@ -20,36 +18,31 @@
 
 
 def test_gettinghtml():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json")
-    body = cherrypy.response.body[0]
-    assert "Paging all foo" in body
+    start_server(root = ExposeRoot())
+    response = go("/with_json")
+    assert "Paging all foo" in response
 
 def test_gettingjson():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json?tg_format=json")
-    body = cherrypy.response.body[0]
-    assert '"title": "Foobar"' in body
+    start_server(root = ExposeRoot())
+    response = go("/with_json?tg_format=json")
+    assert '"title": "Foobar"' in response
 
 def test_gettingjsonviaaccept():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json_via_accept",
+    start_server(root = ExposeRoot())
+    response = go("/with_json_via_accept",
             headers=dict(Accept="text/javascript"))
-    body = cherrypy.response.body[0]
-    assert '"title": "Foobar"' in body
+    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
+    start_server(root = ExposeRoot())
+    response = go("/with_json_via_accept?tg_format=json")
+    assert '"title": "Foobar"' in response
 
 def test_getting_plaintext():
-    cherrypy.root = ExposeRoot()
-    create_request("/with_json_via_accept",
+    start_server(root = ExposeRoot())
+    response = go("/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."
+    assert response.body == "This is a plain text for foo."
 
 def test_allow_json():
 
@@ -58,16 +51,14 @@
         def test(self):
             return dict(title="Foobar", mybool=False, someval="niggles")
 
-    cherrypy.root = NewRoot()
-    create_request("/test", headers= dict(accept="text/javascript"))
-    body = cherrypy.response.body[0]
-    values = simplejson.loads(body)
+    start_server(root = NewRoot())
+    response = go("/test", headers= dict(accept="text/javascript"))
+    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"
-    create_request("/test?tg_format=json")
-    body = cherrypy.response.body[0]
-    values = simplejson.loads(body)
+    assert response.headers["Content-Type"] == "text/javascript"
+    response = go("/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"
Index: turbogears/tests/test_controllers.py
===================================================================
--- turbogears/tests/test_controllers.py	(revision 4621)
+++ turbogears/tests/test_controllers.py	(working copy)
@@ -1,10 +1,15 @@
 import unittest
-import formencode
-import cherrypy
-import pkg_resources
+import urllib
+import turbogears
 from turbogears import config, controllers, database, \
     error_handler, exception_handler, expose, flash, redirect, \
     startup, testutil, url, validate, validators
+from turbogears.testutil import go
+import simplejson
+import formencode
+import cherrypy
+import pkg_resources
+from nose.tools import eq_
 
 
 class SubApp(controllers.RootController):
@@ -13,18 +18,39 @@
     def index(self):
         return url("/Foo/")
 
+    @expose()
+    def foo(self):
+        return url("foo")
 
+    @expose()
+    def foo2(self):
+        return url("/foo")
+
+    @expose()
+    def redir(self):
+        turbogears.redirect("/foo")
+
+    @expose()
+    def redir2(self):
+        raise turbogears.redirect("/foo")
+
+
+
 class MyRoot(controllers.RootController):
 
     @expose()
     def index(self):
-        pass
+        return {}
 
     def validation_error_handler(self, tg_source, tg_errors, *args, **kw):
-        self.functionname = tg_source.__name__
-        self.values = kw
         self.errors = tg_errors
-        return "Error Message"
+        errors = {}
+        for (key, value) in tg_errors.items():
+           if hasattr(value, 'msg'):
+               errors[key] = value.msg
+           else:
+               errors[key] = value
+        return dict(msg = "Error Message", values = kw, errors = errors)
 
     @expose(html="turbogears.tests.simple", allow_json=True)
     def test(self):
@@ -36,13 +62,10 @@
 
     @expose()
     def pos(self, posvalue):
-        self.posvalue = posvalue
-        return ""
+        return dict(posvalue = posvalue)
 
     @expose()
-    def servefile(self, tg_exceptions=None):
-        self.servedit = True
-        self.serve_exceptions = tg_exceptions
+    def servefile(self):
         return cherrypy.lib.cptools.serveFile(
             pkg_resources.resource_filename(
                 "turbogears.tests", "test_controllers.py"))
@@ -70,8 +93,7 @@
     @validate(validators={'value': validators.StringBoolean()})
     @error_handler(validation_error_handler)
     def istrue(self, value):
-        self.value = value
-        return str(value)
+        return dict(value=str(value))
 
     @expose()
     @validate(validators={'value': validators.StringBoolean()})
@@ -117,11 +139,12 @@
         "lastname": validators.String()})
     @error_handler(validation_error_handler)
     def save(self, submit, firstname, lastname="Miller"):
-        self.submit = submit
-        self.firstname = firstname
-        self.lastname = lastname
-        self.fullname = "%s %s" % (self.firstname, self.lastname)
-        return self.fullname
+        submit = submit
+        firstname = firstname
+        lastname = lastname
+        fullname = "%s %s" % (firstname, lastname)
+        return dict(firstname = firstname, lastname = lastname, 
+            fullname = fullname, submit = submit)
 
     class Registration(formencode.Schema):
         allow_extra_fields = True
@@ -145,7 +168,10 @@
     rwt_called = 0
     def rwt(self, func, *args, **kw):
         self.rwt_called += 1
-        func(*args, **kw)
+        result = func(*args, **kw)
+        print result
+        result += 'rwt_called = %s' % self.rwt_called
+        return result
 
     @expose(html="turbogears.tests.simple", allow_json=True)
     def flash_plain(self):
@@ -223,38 +249,28 @@
 class TestRoot(unittest.TestCase):
 
     def setUp(self):
-        cherrypy.root = None
-        cherrypy.tree.mount_points = {}
-        cherrypy.tree.mount(MyRoot(), "/")
-        cherrypy.tree.mount(SubApp(), "/subthing")
+        testutil.mount(MyRoot())
+        testutil.mount(SubApp(), '/subthing')
 
-    def 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 = testutil.go("/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])
-        assert values == dict(title="Foobar", mybool=False,
-            someval="niggles", tg_flash=None)
-        assert cherrypy.response.headers["Content-Type"] == "text/javascript"
+        response = testutil.go("/test?tg_format=json")
+        values = simplejson.loads(response.body)
+        eq_(values, dict(title="Foobar", mybool=False,
+            someval="niggles", tg_flash=None))
+        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 = testutil.go("/impliedjson?tg_format=json")
+        assert '"title": "Blah"' in response.body
 
     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 = testutil.go("/allowjson?tg_format=json", status=500)
+        assert response.headers["Content-Type"] == "text/html", response.headers
 
     def test_allow_json_config(self):
         """JSON output can be enabled via config."""
@@ -264,10 +280,10 @@
             def allowjsonconfig(self):
                 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"
-        config.update({'tg.allow_json': False})
+        testutil.mount(JSONRoot())
+        response = testutil.go('/allowjsonconfig?tg_format=json')
+        assert response.headers["Content-Type"]=="text/javascript"
+        config.update({'tg.allow_json':False})
 
     def test_allow_json_config_false(self):
         """Make sure JSON can still be restricted with a global config on."""
@@ -277,227 +293,202 @@
             def allowjsonconfig(self):
                 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"
+        testutil.mount(JSONRoot())
+        response = testutil.go('/allowjson?tg_format=json', status=404)
+        assert response.headers["Content-Type"]=="text/html"
         config.update({'tg.allow_json': False})
 
     def test_json_error(self):
         """The error handler should return JSON if requested."""
-        testutil.create_request("/jsonerror")
-        assert cherrypy.response.headers["Content-Type"] == "text/html; charset=utf-8"
-        assert "Paging all errors" in cherrypy.response.body[0]
-        testutil.create_request("/jsonerror?tg_format=json")
-        assert cherrypy.response.headers["Content-Type"] == "text/javascript"
-        assert '"someval": "errors"' in cherrypy.response.body[0]
+        response = testutil.go("/jsonerror")
+        assert response.headers["Content-Type"] == "text/html; charset=utf-8"
+        assert "Paging all errors" in response
+        response = testutil.go("/jsonerror?tg_format=json")
+        assert response.headers["Content-Type"] == "text/javascript"
+        assert '"someval": "errors"' in response
 
     def test_invalid_return(self):
-        testutil.create_request("/invalid")
-        assert cherrypy.response.status.startswith("500")
+        response = testutil.go("/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")
-        assert not hasattr(cherrypy.root, "errors")
+        config.update({"tg.strict_parameters" : True})
+        response = testutil.go(
+            "/save?submit=save&firstname=Foo&lastname=Bar&badparam=1", 
+            status = 500)
 
     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 = testutil.go("/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")
-        assert not hasattr(cherrypy.root, "errors")
+        response = testutil.go("/test?tg_random=1", status=200)
+        assert "Paging all niggles" in response
+        response = testutil.go("/test?tg_not_random=1", status=500)
 
     def test_ignore_parameters(self):
         config.update({"tg.strict_parameters": True})
-        testutil.create_request("/test?ignore_me=1")
-        assert cherrypy.response.status.startswith("500")
-        assert not hasattr(cherrypy.root, "errors")
+        response = testutil.go("/test?ignore_me=1", status=500)
         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")
-        assert not hasattr(cherrypy.root, "errors")
+        response = testutil.go("/test?ignore_me=1")
+        assert "Paging all niggles" in response
+        response = testutil.go("/test?me_too=1", status=200)
+        assert "Paging all niggles" in response
+        testutil.go("/test?me_not=1", status=500)
+        assert not response.raw.has_key('errors')
 
     def test_retrieve_dict_directly(self):
-        d = testutil.call(cherrypy.root.returnjson)
-        assert d["title"] == "Foobar"
+        d = testutil.go("/returnjson")
+        assert d.raw['title'] == "Foobar"
 
     def test_templateOutput(self):
-        testutil.create_request("/test")
-        assert "Paging all niggles" in cherrypy.response.body[0]
+        response = testutil.go("/test")
+        assert "Paging all niggles" in response
 
+    def test_throw_out_random(self):
+        """A random value can be appended to the URL to avoid caching
+        problems."""
+        response = testutil.go("/test?tg_random=1")
+        assert "Paging all niggles" in response
+
     def test_safari_unicode_fix(self):
-        testutil.create_request("/unicode", headers={'User-Agent':
-            "Apple WebKit Safari/412.2"})
-        firstline = cherrypy.response.body[0].split('\n')[0]
+        response = go("/unicode", headers={'User-Agent' :
+            "Apple WebKit Safari/412.2"}, status='*')
+        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 = testutil.go("/returnjson")
+        assert '"title": "Foobar"' in response
+        response = testutil.go("/returnjson?tg_format=html", status=500)
+        assert '"title": "Foobar"' not in response
 
     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 = testutil.go("/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 = testutil.go("/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 = testutil.go("/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 = testutil.go("/exposetemplate_short")
+        assert "Paging all foo" in response
 
     def test_validation(self):
         """Data can be converted and validated"""
-        testutil.create_request("/istrue?value=true")
-        assert cherrypy.root.value is True
-        testutil.create_request("/istrue?value=false")
-        assert cherrypy.root.value is False
-        cherrypy.root = MyRoot()
-        testutil.create_request("/istrue?value=foo")
-        assert not hasattr(cherrypy.root, "value")
-        assert cherrypy.root.functionname == "istrue"
-        testutil.create_request("/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")
-        assert cherrypy.root.fullname == "Arthur Miller"
-        testutil.create_request("/save?submit=send&firstname=Arthur&lastname=")
-        assert cherrypy.root.fullname == "Arthur "
-        testutil.create_request("/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=")
-        assert len(cherrypy.root.errors) == 1
-        assert cherrypy.root.errors.has_key("firstname")
+        response = testutil.go("/istrue?value=true")
+        assert response.raw['value'] == 'True'
+        response = testutil.go("/istrue?value=false")
+        assert response.raw['value'] == 'False'
+        testutil.start_server(root = MyRoot())
+        response = testutil.go("/istrue?value=foo")
+        assert not response.raw.has_key('value')
 
+        response = testutil.go("/save?submit=send&firstname=John&lastname=Doe")
+        assert response.raw['fullname'] == "John Doe"
+        assert response.raw['submit'] == "send"
+        response = testutil.go("/save?submit=send&firstname=Arthur")
+        assert response.raw['fullname'] == "Arthur Miller"
+        response = testutil.go("/save?submit=send&firstname=Arthur&lastname=")
+        assert response.raw['fullname'] == "Arthur "
+        response = testutil.go("/save?submit=send&firstname=D&lastname=")
+        assert len(response.raw['errors'].keys()) == 1
+        assert response.raw['errors'].has_key("firstname")
+        assert "characters" in response.raw['errors']["firstname"].lower()
+        response = testutil.go("/save?submit=send&firstname=&lastname=")
+        assert len(response.raw['errors'].keys()) == 1
+        assert response.raw['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")
-        assert cherrypy.root.value is None
-        testutil.create_request("/errorchain?value=notbool")
-        assert cherrypy.root.value == 'notbool'
+        response = testutil.go("/errorchain?value=true")
+        assert not hasattr(response, 'raw')
+        response = testutil.go("/errorchain?value=notbool")
+        assert response.raw['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")
-        assert cherrypy.root.value == 'True'
-        testutil.create_request("/nestedcall?value=false")
-        assert cherrypy.root.value == 'False'
+        response = testutil.go("/nestedcall?value=true")
+        assert response.raw['value'] == 'True'
+        response = testutil.go("/nestedcall?value=false")
+        assert response.raw['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")
-        assert cherrypy.root.fullname == "Joe Doe"
-        assert cherrypy.root.submit == "send"
-        testutil.create_request("/save2?submit=send&firstname=Arthur&lastname=")
-        assert cherrypy.root.fullname == "Arthur "
-        testutil.create_request("/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=")
-        assert len(cherrypy.root.errors) == 1
-        assert cherrypy.root.errors.has_key("firstname")
+        """Data can be converted and validated with formencode.Schema instance"""
+        response = testutil.go("/save2?submit=send&firstname=Joe&lastname=Doe")
+        assert response.raw['fullname'] == "Joe Doe"
+        assert response.raw['submit'] == "send"
+        response = testutil.go("/save2?submit=send&firstname=Arthur&lastname=")
+        assert response.raw['fullname'] == "Arthur "
+        response = testutil.go("/save2?submit=send&firstname=&lastname=")
+        assert len(response.raw['errors'].keys()) == 1
+        assert response.raw['errors'].has_key("firstname")
+        response = testutil.go("/save2?submit=send&firstname=D&lastname=")
+        assert len(response.raw['errors'].keys()) == 1
+        assert response.raw['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 = testutil.go("/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 = testutil.go("/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")
+        response = testutil.go("/nestedcall?value=true")
         database.run_with_transaction = oldrwt
-        assert cherrypy.root.value
-        assert cherrypy.root.rwt_called == 1
+        assert response.raw['value']
+        assert 'rwt_called = 1' in response
 
     def test_positional(self):
         """Positional parameters should work"""
-        testutil.create_request("/pos/foo")
-        assert cherrypy.root.posvalue == "foo"
+        response = testutil.go("/pos/foo")
+        assert response.raw['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 = testutil.go("/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.cookies_set.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 = testutil.go("/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.cookies_set.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 = go("/flash_redirect?tg_format=json", status=302)
+        response = go(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 = go("/flash_redirect_with_trouble_chars?tg_format=json", status=302)
+        response = go(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,64 +496,53 @@
         # 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",
-            headers=dict(Cookie='tg_flash="old flash"; Path=/;'))
-        import simplejson
-        values = simplejson.loads(cherrypy.response.body[0])
+        response = go("/flash_plain?tg_format=json",
+            headers= {'Cookie':'tg_flash="old flash"; Path=/;'})
+        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
+        eq_(response.cookies_set['tg_flash'], "")
 
     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 = testutil.go('/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 = testutil.go('/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 = testutil.go('/test')
         assert  '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML ' in response
         assert 'This is the groovy test ' in response
         assert '    ' not in response
 
     def test_fileserving(self):
-        #outputcap = StringIO()
-        #sys.stdout = outputcap
-        testutil.create_request("/servefile")
-        assert cherrypy.root.servedit
-        assert not cherrypy.root.serve_exceptions
-        #assert "AssertionError" not in outputcap.getvalue()
+        response = testutil.go("/servefile")
+        assert "test_fileserving" in response
 
     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 = testutil.go("/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 = testutil.go(
             "/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 = testutil.go("/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 = testutil.go("/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 +551,23 @@
         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 = testutil.go("/raise_all_exc?num=1")
+        assert 'handling_value' in response
+        response = testutil.go("/raise_all_exc?num=2")
+        assert 'handling_index' in response
+        response = testutil.go("/raise_all_exc?num=3")
+        assert 'handling_key' in response
 
 
 class TestURLs(unittest.TestCase):
 
     def setUp(self):
-        cherrypy.tree.mount_points = {}
-        cherrypy.root = MyRoot()
-        cherrypy.root.subthing = SubApp()
-        cherrypy.root.subthing.subsubthing = SubApp()
+        testutil.mount(MyRoot())
+        testutil.mount(SubApp(), '/subthing')
+        testutil.mount(SubApp(), '/subthing/subsubthing')
 
     def test_basic_urls(self):
-        testutil.create_request("/")
+        testutil.go("/")
         assert "/foo" == url("/foo")
         assert "foo/bar" == url(["foo", "bar"])
         assert url("/foo", bar=1, baz=2) in \
@@ -602,37 +581,34 @@
         assert url("/foo") == "/foo"
 
     def test_approots(self):
-        testutil.create_request("/subthing/")
-        assert url("foo") == "foo"
-        assert url("/foo") == "/subthing/foo"
+        response = testutil.go("/subthing/foo")
+        self.failUnlessEqual("foo", response.body)
+          
+        response = testutil.go("/subthing/foo2")
+        self.failUnlessEqual("/subthing/foo", response.body)
 
     def test_lower_approots(self):
-        testutil.create_request("/subthing/subsubthing/")
-        assert url("/foo") == "/subthing/subsubthing/foo"
+        config.update({"server.webpath" : "/XXX"})
+        response = testutil.go("/subthing/subsubthing/")
+        eq_("/XXX/subthing/subsubthing/Foo/", response.body)
 
-    def test_approots_With_path(self):
-        config.update({"server.webpath": "/coolsite/root"})
-        startup.startTurboGears()
-        testutil.create_request("/coolsite/root/subthing/")
-        assert url("/foo") == "/coolsite/root/subthing/foo"
+    def test_approots_with_path(self):
+        config.update({"server.webpath" : "/coolsite/root"})
+        testutil.start_server()
+        response = testutil.go("/coolsite/root/subthing/")
+        self.failUnlessEqual("/coolsite/root/subthing/Foo/", response.body)
 
     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
+        config.update({"server.webpath" : "/coolsite/root"})
+        testutil.start_server()
+        response = go("/coolsite/root/subthing/redir")
+        eq_(response.location, 'http://localhost:80/coolsite/root/subthing/foo')
 
+        response = go("/coolsite/root/subthing/redir2")
+        eq_(response.location, 'http://localhost:80/coolsite/root/subthing/foo')
+
     def test_multi_values(self):
-        testutil.create_request("/")
+        testutil.go("/")
         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 +616,7 @@
 
     def test_unicode(self):
         """url() can handle unicode parameters"""
-        testutil.create_request("/")
+        testutil.go("/")
         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,22 +626,23 @@
 
     def test_list(self):
         """url() can handle list parameters, with unicode too"""
-        testutil.create_request("/")
+        testutil.go("/")
         assert url('/', foo=['bar', u'\N{LATIN SMALL LETTER A WITH GRAVE}']
             ) == '/?foo=bar&foo=%C3%A0'
 
     def tearDown(self):
         config.update({"server.webpath": ""})
-        startup.startTurboGears()
+        testutil.start_server()
 
 
 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")
+    testutil.mount(SubApp())
+    testutil.mount(SubApp(), '/noslash')
+    response = testutil.go("/noslash")
+    assert response.status.startswith("302")
 
+
 def test_can_use_internally_defined_arguments():
     """Can use argument names that are internally used by TG in controllers"""
 
@@ -675,12 +652,11 @@
         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
+    testutil.mount(App())
+    response = testutil.go("/?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"""
Index: turbogears/tests/test_view.py
===================================================================
--- turbogears/tests/test_view.py	(revision 4621)
+++ turbogears/tests/test_view.py	(working copy)
@@ -1,5 +1,4 @@
 # -*- coding: utf-8 -*-
-
 from turbogears import view, config, testutil
 import cherrypy
 import unittest
@@ -7,7 +6,7 @@
 class TestView(unittest.TestCase):
 
     def setUp(self):
-        testutil.start_cp()
+        testutil.start_server()
         cherrypy.serving.request = testutil.DummyRequest()
         cherrypy.serving.response = testutil.DummyResponse()
 
Index: turbogears/tests/test_testutil.py
===================================================================
--- turbogears/tests/test_testutil.py	(revision 4621)
+++ turbogears/tests/test_testutil.py	(working copy)
@@ -21,7 +21,8 @@
 
 
 def test_browser_session():
-    cherrypy.root = MyRoot()
+    testutil.stop_server()
+    testutil.start_server(MyRoot())
     bs = testutil.BrowsingSession()
     bs.goto('/get_name')
     assert bs.response == 'cookie not found'
@@ -30,7 +31,8 @@
     assert bs.response == 'me'
 
 def test_browser_session_for_two_users():
-    cherrypy.root = MyRoot()
+    testutil.stop_server()
+    testutil.start_server(MyRoot())
     bs1 = testutil.BrowsingSession()
     bs2 = testutil.BrowsingSession()
     bs1.goto('/set_name?name=bs1')
Index: turbogears/tests/test_catwalk.py
===================================================================
--- turbogears/tests/test_catwalk.py	(revision 4621)
+++ turbogears/tests/test_catwalk.py	(working copy)
@@ -3,7 +3,6 @@
 from turbogears import testutil
 from turbogears import controllers
 from turbogears.toolbox.catwalk import CatWalk
-import cherrypy
 import simplejson
 from catwalk_models import browse
 import timeit
@@ -43,37 +42,35 @@
 class Browse(unittest.TestCase):
     def setUp(self):
         browse_data(browse)
-        cherrypy.root = MyRoot()
+        testutil.start_server()
+        testutil.mount(MyRoot(), "/")
+        testutil.mount(CatWalk(browse), '/catwalk')
 
+    def tearDown(self):
+        testutil.stop_server()
+
     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 = testutil.go("/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 = testutil.go("/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 = testutil.go("/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 = testutil.go("/catwalk/browse/?object_name=Song&filters=album:1&tg_format=json")
+        #values = simplejson.loads(response.body)
+        response.headers['Content-Type'] = 'application/json'
+        values = response.json
         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 = testutil.go("/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')
@@ -86,42 +83,34 @@
     def test_rows_joins_count(self):
         #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 = testutil.go("/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))
 
     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 = testutil.go("/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 = testutil.go("/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 = testutil.go("/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 = testutil.go("/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']
@@ -131,16 +120,21 @@
     model = browse
 
     def setUp(self):
-        cherrypy.root = MyRoot()
-        cherrypy.root.catwalk = CatWalk(browse)
+        testutil.start_server()
+        testutil.mount(MyRoot(), "/")
+        testutil.mount(CatWalk(browse), '/catwalk')
         testutil.DBTest.setUp(self)
         browse_data(browse)
 
+    def tearDown(self):
+        testutil.stop_server()
+        testutil.DBTest.tearDown(self)
+
     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")
+        testutil.go("/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")
+        testutil.go("/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 4621)
+++ turbogears/tests/test_paginate.py	(working copy)
@@ -8,7 +8,7 @@
 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 go, sqlalchemy_cleanup, start_server
 from turbojson.jsonify import jsonify
 
 import cherrypy
@@ -218,48 +218,43 @@
 
 
     def setUp(self):
-        cherrypy.root = self.MyRoot()
+        start_server(self.MyRoot())
 
     def test_spy(self):
-        create_request('/spy')
-        body = cherrypy.response.body[0]
-        Spy.assert_ok(body, 'current_page', 1)
+        response = go('/spy')
+        Spy.assert_ok(response, 'current_page', 1)
         try:
-            Spy.assert_ok(body, 'current_page', 2)
+            Spy.assert_ok(response, '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 = go('/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 = go('/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 = go('/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 = go('/spy_correct_expectation')
+        Spy.assert_ok(response, 'var_name', 'data')
+        Spy.assert_ok(response, 'var_name', "'data'", raw=True)
 
 
 class TestPagination(unittest.TestCase):
     """Base class for all Paginate TestCases"""
 
-    def request(self, url):
-        create_request(url)
-        self.body = cherrypy.response.body[0]
+    def request(self, url, status=None):
+        self.body = go(url, status=status).body
         if "fail: " in self.body:
             print self.body
-            assert False, "Spy alert! Check body output for details..."
+            assert False, "Spy alert! Check response output for details..."
 
 
 class TestBasicPagination(TestPagination):
@@ -343,7 +338,7 @@
 
 
     def setUp(self):
-        cherrypy.root = self.MyRoot()
+        start_server(root = self.MyRoot())
 
     def test_pagination_old_style(self):
         self.request("/basic")
@@ -439,8 +434,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,7 +748,7 @@
 
 
     def setUp(self):
-        cherrypy.root = self.MyRoot()
+        start_server(root = self.MyRoot())
 
     def assert_order(self, *args):
         expr = 'data="%s"' % ''.join(['[Address %r]' % x for x in args])
@@ -826,8 +820,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/qstemplates/quickstart/+package+/tests/test_controllers.py_tmpl
===================================================================
--- turbogears/qstemplates/quickstart/+package+/tests/test_controllers.py_tmpl	(revision 4621)
+++ turbogears/qstemplates/quickstart/+package+/tests/test_controllers.py_tmpl	(working copy)
@@ -4,36 +4,34 @@
 from ${package}.controllers import Root
 import cherrypy
 
-cherrypy.root = Root()
+testutil.mount(root = Root())
 
 class TestPages(unittest.TestCase):
 
     def setUp(self):
-        turbogears.startup.startTurboGears()
+        testutil.start_server()
 
     def tearDown(self):
         """Tests for apps using identity need to stop CP/TG after each test to
         stop the VisitManager thread.
         See http://trac.turbogears.org/turbogears/ticket/1217 for details.
         """
-        turbogears.startup.stopTurboGears()
+        testutil.stop_server()
 
     def test_method(self):
         "the index method should return a string called now"
         import types
-        result = testutil.call(cherrypy.root.index)
-        assert type(result["now"]) == types.StringType
+        response = testutil.go("/")
+        assert type(response.raw["now"]) == types.StringType
 
     def test_indextitle(self):
         "The indexpage should have the right title"
-        testutil.create_request("/")
-        response = cherrypy.response.body[0].lower()
-        assert "<title>welcome to turbogears</title>" in response
+        response = testutil.go("/")
+        assert "<title>welcome to turbogears</title>" in response.body.lower()
 
 #if $identity != "none"
     def test_logintitle(self):
         "login page should have the right title"
-        testutil.create_request("/login")
-        response = cherrypy.response.body[0].lower()
-        assert "<title>login</title>" in response
+        response = testutil.go("/login")
+        assert "<title>login</title>" in response.body.lower()
 #end if
Index: turbogears/testutil.py
===================================================================
--- turbogears/testutil.py	(revision 4621)
+++ turbogears/testutil.py	(working copy)
@@ -4,9 +4,15 @@
 import unittest
 import Cookie
 import cStringIO as StringIO
+from turbogears.util import deprecated
+from webtest import TestApp
 
 import cherrypy
-from cherrypy import _cphttptools
+cherrypy_major_ver = int(cherrypy.__version__.split('.')[0])
+if cherrypy_major_ver < 3:
+    from cherrypy._cphttptools import Request, Response
+else:
+    from cherrypy import Request, Response
 
 try:
     import sqlobject
@@ -50,21 +56,66 @@
 config.update({'global':
         {'autoreload.on': False, 'tg.new_style_logging': True}})
 
+def unmount():
+    """Remove an application from the object traversal tree."""
+    # There's no clean way to remove a subtree under CP2, so the only use case
+    #  handled here is to remove the entire application.
+    # Supposedly, you can do a partial unmount with CP3 using:
+    #  del cherrypy.tree.apps[path]
+    cherrypy.root = None
+    cherrypy.tree.mount_points = {}
 
-def start_cp():
-    if not config.get("cherrypy_started", False):
-        cherrypy.server.start(serverClass=None, initOnly=True)
-        config.update({"cherrypy_started" : True})
+def mount(app, path="/"):
+    """Mount an app at a path.  Returns True if the server needs
+    to be restarted afterwards.
+    """
+    current = cherrypy.tree.mount_points.get(path, None)
+    if current != app:
+        #Only mount the app if it's not already mounted
+        if current != None:
+            #If there's something else mounted at this path already, we'll
+            # have to unmount it first.  CherryPy can't handle dynamic 
+            # remounting.
+            unmount()
+        cherrypy.tree.mount(app, path)
+    if path == '/':
+        cherrypy.root = app
+    return get_app()
 
+def start_server(root=None):
+    """Start the server if it's not already.  Optionally mount an app first."""
+    if root:
+        mount(root)
+    if not config.get("server_started"):
+        if cherrypy_major_ver < 3:
+            cherrypy.server.start(serverClass=None, initOnly=True)
+        else:
+            cherrypy.server.quickstart()
+            cherrypy.engine.start()
+    startup.startTurboGears()
+    config.update({"server_started" : True})
 
+start_cp = deprecated("start_cp has been superceded by start_server.") \
+               (start_server)
+
+
+def stop_server():
+    if config.get("server_started", True):
+        startup.stopTurboGears()
+        config.update({"server_started" : False})
+        unmount()
+    return
+
 test_user = None
 
+@deprecated()
 def set_identity_user(user):
     """Setup a user for configuring request's identity."""
     global test_user
     test_user = user
 
 
+@deprecated()
 def attach_identity(req):
     if config.get("identity.on", False):
         req.identity = (test_user
@@ -72,10 +123,11 @@
             or current_provider.anonymous_identity())
 
 
+@deprecated("Use testutil.go instead of create_request")
 def create_request(request, method="GET", protocol="HTTP/1.1",
         headers={}, rfile=None, clientAddress="127.0.0.1",
         remoteHost="localhost", scheme="http"):
-    start_cp()
+    start_server()
     if not rfile:
         rfile = StringIO.StringIO("")
     if type(headers) != dict:
@@ -86,13 +138,35 @@
     if not hasattr(cherrypy.root, "started"):
         startup.startTurboGears()
         cherrypy.root.started = True
-    req = _cphttptools.Request(clientAddress, 80, remoteHost, scheme)
+    req = Request(clientAddress, 80, remoteHost, scheme)
     cherrypy.serving.request = req
     attach_identity(req)
-    cherrypy.serving.response = _cphttptools.Response()
+    cherrypy.serving.response = Response()
     req.run(" ".join((method, request, protocol)), headerList, rfile)
+    return cherrypy.serving.response
 
+createRequest = create_request
 
+
+def get_app():
+    """Return a WSGI application from cherrypy's root object."""
+    if cherrypy_major_ver < 3:
+        app = cherrypy._cpwsgi.wsgiApp
+    else:
+        app = cherrypy.tree.mount(cherrypy.root, '/')
+        # app = cherrypy.wsgi.CPWSGIServer
+    return app
+
+
+def go(request, headers={}, cookies={}, status=None):
+    """Visit a site with WebTest, returning the response."""
+    start_server()
+    app = TestApp(get_app())
+    response = app.get(request, headers=headers, status=status)
+    response.app = app
+    return response
+
+
 class BrowsingSession(object):
 
     def __init__(self):
@@ -103,12 +177,12 @@
     def goto(self, *args, **kwargs):
         if self.cookie:
             headers = kwargs.setdefault('headers', {})
-            headers['Cookie'] = self.cookie.output()
-        create_request(*args, **kwargs)
-        self.response = cherrypy.response.body[0]
-        self.status = cherrypy.response.status
-        if cherrypy.response.simple_cookie:
-            self.cookie.update(cherrypy.response.simple_cookie)
+            headers['Cookie'] = self.cookie_encoded
+        response = go(*args, **kwargs)
+        self.response = response.body
+        self.status = response.status
+        self.cookie = response.cookies_set
+        self.cookie_encoded = response.headers.get('Set-Cookie', '')
 
 
 def _return_directly(output, *args):
@@ -140,14 +214,16 @@
 class DummyResponse:
     """A very simple dummy response."""
     headers = {}
+    
 
-
+@deprecated("Use testutil.go instead of call")
 def call(method, *args, **kw):
-    start_cp()
+    start_server()
     output, response = call_with_request(method, DummyRequest(), *args, **kw)
     return output
 
 
+@deprecated("Use testutil.go instead of call_with_request")
 def call_with_request(method, request, *args, **kw):
     """More fine-grained version of call method.
 
@@ -156,7 +232,7 @@
     """
     orig_proc_output = controllers._process_output
     controllers._process_output = _return_directly
-    cherrypy.serving.response = _cphttptools.Response()
+    cherrypy.serving.response = Response()
     cherrypy.serving.request = request
     if not hasattr(request, "identity"):
         attach_identity(request)
@@ -201,10 +277,6 @@
                 item.dropTable(ifExists=True)
 
 
-def reset_cp():
-    cherrypy.root = None
-
-
 def catch_validation_errors(widget, value):
     """Catch and unpack validation errors (for testing purposes)."""
     try:
@@ -253,7 +325,7 @@
 
     """
     global _currentcat
-    assert not _currentcat
+    assert not _currentcat, "_currentcat not cleared.  Use print_log to reset."
     if not isinstance(category, list) and not isinstance(category, tuple):
         category = [category]
     _currentcat = category
@@ -305,6 +377,7 @@
     sqlalchemy.orm.clear_mappers()
 
 
-__all__ = ["call", "create_request", "DBTest",
-    "attach_identity", "set_identity_user",
-    "capture_log", "print_log", "get_log", "sqlalchemy_cleanup"]
+__all__ = ["call", "create_request", "DBTest", "attach_identity", 
+    "set_identity_user", "capture_log", "print_log", "get_log",
+    "sqlalchemy_cleanup", "mount", "go", "start_server", "stop_server"]
+
Index: turbogears/widgets/tests/test_nested_form_controllers.py
===================================================================
--- turbogears/widgets/tests/test_nested_form_controllers.py	(revision 4621)
+++ turbogears/widgets/tests/test_nested_form_controllers.py	(working copy)
@@ -1,5 +1,4 @@
 import turbogears
-import cherrypy
 from turbogears import widgets
 from turbogears import controllers
 from turbogears import validators
@@ -17,20 +16,22 @@
 
 class MyRoot(controllers.RootController):
     def testform(self, p_data, tg_errors=None):
-        if tg_errors:
-            self.has_errors = True
-        self.name = p_data['name']
-        self.age = p_data['age']
+        has_errors = tg_errors is not None
+        name = p_data['name']
+        age = p_data['age']
+        return dict(has_errors = has_errors, name=name, age = age)
     testform = turbogears.validate(form=myform)(testform)
-    testform = turbogears.expose(html="turbogears.tests.othertemplate")(
+    testform = turbogears.expose(template="turbogears.tests.othertemplate")(
                                  testform)
 
     def set_errors(self):
-        self.has_errors = True
+        return dict(has_errors = True)
+        
 
     def testform_new_style(self, p_data):
-        self.name = p_data['name']
-        self.age = p_data['age']
+        name = p_data['name']
+        age = p_data['age']
+        return dict(name = name, age = age)
     testform_new_style = turbogears.validate(form=myform)(testform_new_style)
     testform_new_style = turbogears.error_handler(set_errors)(testform_new_style)
     testform_new_style = turbogears.expose()(testform_new_style)
@@ -38,15 +39,14 @@
 
 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?p_data.name=ed&p_data.age=5")
-    assert root.name == "ed"
-    print root.age
-    assert root.age == 5
+    testutil.mount(MyRoot())
+    response = testutil.go("/testform_new_style?p_data.name=ed&p_data.age=5")
+    assert response.raw['name'] == "ed"
+    print response.raw['age']
+    assert response.raw['age'] == 5
 
 def test_invalid_form_with_error_handling():
     "Invalid forms can be handled by the method"
-    root = cherrypy.root
-    testutil.create_request("/testform_new_style?p_data.name=ed&p_data.age=edalso")
-    assert root.has_errors
+    testutil.mount(MyRoot())
+    response = testutil.go("/testform_new_style?p_data.name=ed&p_data.age=edalso")
+    assert response.raw['has_errors']
Index: turbogears/widgets/tests/test_datagrid.py
===================================================================
--- turbogears/widgets/tests/test_datagrid.py	(revision 4621)
+++ turbogears/widgets/tests/test_datagrid.py	(working copy)
@@ -2,7 +2,7 @@
 from turbogears.widgets.datagrid import DataGrid
 
 def setup_module():
-    testutil.start_cp()   
+    testutil.start_server()   
 
 class Foo:
 
Index: turbogears/widgets/tests/test_link_inclusion.py
===================================================================
--- turbogears/widgets/tests/test_link_inclusion.py	(revision 4621)
+++ turbogears/widgets/tests/test_link_inclusion.py	(working copy)
@@ -1,6 +1,4 @@
 import turbogears
-import cherrypy
-
 from turbogears import widgets, testutil
 
 
@@ -21,12 +19,11 @@
             return dict(form=form)
         test = turbogears.expose(template="turbogears.widgets.tests.form")(test)
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test")
-    output = cherrypy.response.body[0]
-    assert 'foo.js' in output
-    assert "alert('hello');" in output
-    assert 'foo.css' in output
+    testutil.start_server(root = MyRoot())
+    response = testutil.go("/test")
+    assert 'foo.js' in response.body
+    assert "alert('hello');" in response.body
+    assert 'foo.css' in response.body
 
 
 def test_calendardatepicker_js():
@@ -37,45 +34,40 @@
             return dict(widget=widgets.CalendarDatePicker(calendar_lang=lang))
         test = turbogears.expose(template="turbogears.widgets.tests.widget")(test)
 
-    cherrypy.root = MyRoot()
+    testutil.start_server(root = MyRoot())
 
     # testing default language (en)
-    testutil.create_request("/test")
-    output = cherrypy.response.body[0]
-    assert 'calendar/calendar.js' in output
-    assert 'calendar/calendar-setup.js' in output
-    assert 'calendar/lang/calendar-en.js' in output
+    response = testutil.go("/test")
+    assert 'calendar/calendar.js' in response.body
+    assert 'calendar/calendar-setup.js' in response.body
+    assert 'calendar/lang/calendar-en.js' in response.body
 
     # testing non-existing language
-    testutil.create_request("/test",
+    response = testutil.go("/test",
         headers={"Accept-Language": "x"})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-x.js' not in output
-    assert 'calendar/lang/calendar-en.js' in output
+    assert 'calendar/lang/calendar-x.js' not in response.body
+    assert 'calendar/lang/calendar-en.js' in response.body
 
     # testing French language
-    testutil.create_request("/test",
+    response = testutil.go("/test",
         headers={"Accept-Language": "fr"})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-fr.js' in output
-    assert 'calendar/lang/calendar-en.js' not in output
-    assert 'charset="utf-8"' in output
+    assert 'calendar/lang/calendar-fr.js' in response.body
+    assert 'calendar/lang/calendar-en.js' not in response.body
+    assert 'charset="utf-8"' in response.body
 
     # testing German language with any charset
-    testutil.create_request("/test",
+    response = testutil.go("/test",
         headers={"Accept-Language": "de", "Accept-Charset": "*"})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-de.js' in output
-    assert 'calendar/lang/calendar-en.js' not in output
-    assert 'charset="*"' not in output
+    assert 'calendar/lang/calendar-de.js' in response.body
+    assert 'calendar/lang/calendar-en.js' not in response.body
+    assert 'charset="*"' not in response.body
 
     # testing Turkish language with non-existing charset
-    testutil.create_request("/test",
+    response = testutil.go("/test",
         headers={"Accept-Language": "tr", "Accept-Charset": "big5"})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-tr.js' in output
-    assert 'calendar/lang/calendar-en.js' not in output
-    assert 'charset="big5"' not in output
+    assert 'calendar/lang/calendar-tr.js' in response.body
+    assert 'calendar/lang/calendar-en.js' not in response.body
+    assert 'charset="big5"' not in response.body
 
     win1254 = 'windows-1254'
     from codecs import lookup
@@ -85,34 +77,30 @@
         win1254 = 'cp1254' # cannot test name normalization here
 
     # testing Turkish language with existing, not normalized charset
-    testutil.create_request("/test",
+    response = testutil.go("/test",
         headers={"Accept-Language": "tr", "Accept-Charset": win1254})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-tr-cp1254.js' in output
-    assert 'calendar/lang/calendar-en.js' not in output
-    assert 'charset="cp1254"' in output
+    assert 'calendar/lang/calendar-tr-cp1254.js' in response.body
+    assert 'calendar/lang/calendar-en.js' not in response.body
+    assert 'charset="cp1254"' in response.body
 
     # testing more than one language and charset
-    testutil.create_request("/test", headers={"Accept-Language": "x,tr,de,fr",
+    response = testutil.go("/test", headers={"Accept-Language": "x,tr,de,fr",
         "Accept-Charset": "big5,%s,latin-1" % win1254})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-tr-cp1254.js' in output
-    assert 'calendar/lang/calendar-x.js' not in output
-    assert 'calendar/lang/calendar-en.js' not in output
-    assert 'charset="cp1254"' in output
-    assert 'charset="big5"' not in output
+    assert 'calendar/lang/calendar-tr-cp1254.js' in response.body
+    assert 'calendar/lang/calendar-x.js' not in response.body
+    assert 'calendar/lang/calendar-en.js' not in response.body
+    assert 'charset="cp1254"' in response.body
+    assert 'charset="big5"' not in response.body
 
     # testing predetermined language (fr)
-    testutil.create_request("/test?lang=fr",
+    response = testutil.go("/test?lang=fr",
         headers={"Accept-Language": "de,en,tr"})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-fr.js' in output
-    assert 'calendar/lang/calendar-en.js' not in output
+    assert 'calendar/lang/calendar-fr.js' in response.body
+    assert 'calendar/lang/calendar-en.js' not in response.body
 
     # testing predetermined non-existing language
-    testutil.create_request("/test?lang=x",
+    response = testutil.go("/test?lang=x",
         headers={"Accept-Language": "de,en,fr,tr"})
-    output = cherrypy.response.body[0]
-    assert 'calendar/lang/calendar-de.js' in output
-    assert 'calendar/lang/calendar-x.js' not in output
-    assert 'calendar/lang/calendar-en.js' not in output
+    assert 'calendar/lang/calendar-de.js' in response.body
+    assert 'calendar/lang/calendar-x.js' not in response.body
+    assert 'calendar/lang/calendar-en.js' not in response.body
Index: turbogears/widgets/tests/test_new_validation.py
===================================================================
--- turbogears/widgets/tests/test_new_validation.py	(revision 4621)
+++ turbogears/widgets/tests/test_new_validation.py	(working copy)
@@ -1,27 +1,8 @@
-import cherrypy
 import turbogears.widgets as widgets
 import turbogears.validators as validators
 
 from turbogears.testutil import catch_validation_errors
 
-class Request:
-    validation_errors = {}
-
-oldrequest = None
-
-def setup_module():
-    global oldrequest
-    oldrequest = cherrypy.request
-    cherrypy.request = Request()
-
-
-def teardown_module():
-    global oldrequest
-    cherrypy.request = oldrequest
-
-
-
-
 class SimpleSchema(validators.Schema):
     phone = validators.Int()
 
Index: turbogears/widgets/tests/test_forms.py
===================================================================
--- turbogears/widgets/tests/test_forms.py	(revision 4621)
+++ turbogears/widgets/tests/test_forms.py	(working copy)
@@ -4,12 +4,12 @@
 from turbogears import widgets, validators, controllers, expose, validate, \
                        testutil
 from turbogears import config
-from turbogears.testutil import catch_validation_errors, create_request
+from turbogears.testutil import catch_validation_errors, go
 import cherrypy
 
 
 def setup_module():
-    testutil.start_cp()
+    testutil.start_server()
     cherrypy.serving.request = testutil.DummyRequest()
 
 def test_rendering():
@@ -167,40 +167,42 @@
     @expose()
     @validate(form=nestedform)
     def checkform(self, foo):
-        self.foo = foo
+        return foo
 
+    @expose()
+    def field_for(self):
+        cherrypy.request.validation_errors = dict(foo=dict(foo='error'))
+        template = """\
+        <div xmlns:py="http://purl.org/kid/ns#">
+            ${field_for('foo').fq_name}.appears
+            ${field_for('foo').error}_appears
+            ${field_for('foo').field_id}_appears
+            ${field_for('foo').display(value_for('foo'), **params_for('foo'))}
+        </div>
+        """
+        textfield = widgets.TextField("foo")
+        fieldset = widgets.FieldSet("foo", fields=[textfield], template=template)
+        form = widgets.Form("form", fields=[fieldset], template=template)
 
+        # Good example below of how you can pass parameters and values to nested
+        # widgets.
+        value = dict(foo=dict(foo="HiYo!"))
+        params = dict(attrs=dict(foo=dict(foo=dict(size=100))))
+        params['format'] = 'xhtml'
+        return form.render(value, **params)
+
+
 def test_nested_variables():
-    newroot = NestedController()
-    cherrypy.root = None
-    cherrypy.tree.mount_points = {}
-    cherrypy.tree.mount(newroot, "/")
+    testutil.start_server(NestedController())
     url = u"/checkform?foo.name=Kevin&foo.age=some%20Numero".encode("utf-8")
-    create_request(url)
+    request = go(url)
     assert config.get("decoding_filter.encoding", path="/") == "utf8"
-    assert newroot.foo
-    assert newroot.foo["name"] == "Kevin"
-    assert newroot.foo["age"] == u"some Numero"
+    assert request.raw["name"] == "Kevin"
+    assert request.raw["age"] == u"some Numero"
 
 def test_field_for():
-    cherrypy.request.validation_errors = dict(foo=dict(foo='error'))
-    template = """\
-    <div xmlns:py="http://purl.org/kid/ns#">
-        ${field_for('foo').fq_name}.appears
-        ${field_for('foo').error}_appears
-        ${field_for('foo').field_id}_appears
-        ${field_for('foo').display(value_for('foo'), **params_for('foo'))}
-    </div>
-    """
-    textfield = widgets.TextField("foo")
-    fieldset = widgets.FieldSet("foo", fields=[textfield], template=template)
-    form = widgets.Form("form", fields=[fieldset], template=template)
-    # Good example below of how you can pass parameters and values to nested
-    # widgets.
-    value = dict(foo=dict(foo="HiYo!"))
-    params = dict(attrs=dict(foo=dict(foo=dict(size=100))))
-    params['format'] = 'xhtml'
-    output = form.render(value, **params)
+    response = testutil.go('/field_for')
+    output = response.body
     assert "form_foo_appears" in output
     assert "form_foo_foo_appears" in output
     assert "foo.appears" in output
Index: turbogears/widgets/tests/test_request_related_features.py
===================================================================
--- turbogears/widgets/tests/test_request_related_features.py	(revision 4621)
+++ turbogears/widgets/tests/test_request_related_features.py	(working copy)
@@ -1,9 +1,8 @@
 import re
 import turbogears
 import cherrypy
-
 import turbogears.testutil as testutil
-from turbogears import widgets, validators
+from turbogears import widgets, validators, validate, expose, error_handler
 
 def test_required_fields():
     """
@@ -20,9 +19,9 @@
             return dict(form=form)
         test = turbogears.expose(template="turbogears.widgets.tests.form")(test)
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test")
-    output = cherrypy.response.body[0].lower()
+    testutil.mount(MyRoot())
+    response = testutil.go("/test")
+    output = response.body.lower()
 
     print output
     name_p = 'name="comment"'
@@ -48,39 +47,48 @@
     form2 = widgets.TableForm(name="form2", fields=MyFields())
 
     class MyRoot(turbogears.controllers.RootController):
-        def test(self):
-            return dict(form1=form1, form2=form2)
+       
+        #@expose(template="turbogears.widgets.tests.two_forms") 
+        #@validate(form=form1)
+        #@error_handler('test')
+        def test(self, age, email):
+            validated_form = cherrypy.request.validated_form.name
+            form1_valid = form1.is_validated
+            form2_valid = form2.is_validated
+            return dict(form1=form1, form2=form2, validated_form=validated_form,
+                        form1_valid=form1_valid, form2_valid=form2_valid)
         test = turbogears.expose(template="turbogears.widgets.tests.two_forms")(test)
         test = turbogears.validate(form=form1)(test)
         test = turbogears.error_handler(test)(test)
 
+
         def test2(self):
-            return dict(form=form2)
+            validated_form = cherrypy.request.validated_form.name
+            form1_valid = form1.is_validated
+            form2_valid = form2.is_validated
+            return dict(form=form2, validated_form=validated_form,
+                        form1_valid=form1_valid, form2_valid=form2_valid)
         test2 = turbogears.expose(template="turbogears.widgets.tests.form")(test2)
         test2 = turbogears.validate(form=form2)(test2)
         test2 = turbogears.error_handler(test2)(test2)
 
         def test3(self):
-            return dict(form=form1)
+            validated_form = cherrypy.request.validated_form.name
+            form1_valid = form1.is_validated
+            form2_valid = form2.is_validated
+            return dict(form=form1, validated_form=validated_form,
+                        form1_valid=form1_valid, form2_valid=form2_valid)
         test3 = turbogears.expose(template="turbogears.widgets.tests.form")(test3)
         test3 = turbogears.validate(form=form2)(test3)
         test3 = turbogears.error_handler(test3)(test3)
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test?age=foo&email=bar")
-    output = cherrypy.response.body[0].lower()
-    iv = cherrypy.request.input_values
-    validation_errors = cherrypy.request.validation_errors
-    validated_form = cherrypy.request.validated_form
+    testutil.mount(MyRoot())
+    response = testutil.go("/test?age=foo&email=bar")
+    output = response.body.lower()
 
-    print output
-    print iv
-    print validation_errors
-    print validated_form
-    assert form1 is validated_form
-    assert form1.is_validated
-    assert form2 is not validated_form
-    assert not form2.is_validated
+    assert response.raw['validated_form'] == 'form1'
+    assert response.raw['form1_valid']
+    assert not response.raw['form2_valid']
     value_p = 'value="foo"'
     id_p = 'id="form1_age"'
     assert (re.compile('.*'.join([value_p, id_p])).search(output) or
@@ -92,40 +100,22 @@
             re.compile('.*'.join([id_p, value_p])).search(output)
     )
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test2?age=foo&email=bar")
-    output = cherrypy.response.body[0].lower()
-    iv = cherrypy.request.input_values
-    validation_errors = cherrypy.request.validation_errors
-    validated_form = cherrypy.request.validated_form
+    response = testutil.go("/test2?age=foo&email=bar")
+    output = response.body.lower()
 
-    print output
-    print iv
-    print validation_errors
-    print validated_form
-    assert form1 is not validated_form
-    assert not form1.is_validated
-    assert form2 is validated_form
-    assert form2.is_validated
+    assert response.raw['validated_form'] == 'form2'
+    assert not response.raw['form1_valid']
+    assert response.raw['form2_valid']
     assert 'value="foo"' in output
     assert '>bar<' in output
     assert 'class="fielderror"' in output
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test3?age=foo&email=bar")
-    output = cherrypy.response.body[0].lower()
-    iv = cherrypy.request.input_values
-    validation_errors = cherrypy.request.validation_errors
-    validated_form = cherrypy.request.validated_form
+    response = testutil.go("/test3?age=foo&email=bar")
+    output = response.body.lower()
 
-    print output
-    print iv
-    print validation_errors
-    print validated_form
-    assert form1 is not validated_form
-    assert not form1.is_validated
-    assert form2 is validated_form
-    assert form2.is_validated
+    assert response.raw['validated_form'] == 'form2'
+    assert not response.raw['form1_valid']
+    assert response.raw['form2_valid']
     assert 'value="foo"' not in output
     assert '>bar<' not in output
     assert 'class="fielderror"' not in output
@@ -141,28 +131,28 @@
             name="repeat", fields=MyFields(), repetitions=repetitions)])
 
     class MyRoot(turbogears.controllers.RootController):
-        @turbogears.expose(template="turbogears.widgets.tests.form")
         def test(self):
             return dict(form=form)
+        test = turbogears.expose(template="turbogears.widgets.tests.form")(test)
 
-        @turbogears.expose(template="turbogears.widgets.tests.form")
         def test_value(self):
             value = dict(repeat=[{'name':'foo', 'comment':'hello'},
                                  None,
                                  None,
                                  {'name':'bar', 'comment':'byebye'}])
             return dict(form=form, value=value)
+        test_value = turbogears.expose(template="turbogears.widgets.tests.form")(test_value)
 
         def test_validation(self):
-            return dict(form=form)
-        test_validation = turbogears.expose(
-             template="turbogears.widgets.tests.form")(test_validation)
+            validation_errors = cherrypy.request.validation_errors
+            return dict(form=form, validation_errors=validation_errors)
+        test_validation = turbogears.expose(template="turbogears.widgets.tests.form")(test_validation)
         test_validation = turbogears.validate(form=form)(test_validation)
         test_validation = turbogears.error_handler(test_validation)(test_validation)
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test")
-    output = cherrypy.response.body[0].lower()
+    testutil.mount(MyRoot())
+    response = testutil.go("/test")
+    output = response.body.lower()
     for i in range(repetitions):
         assert 'id="form_repeat_%i"' % i in output
         assert 'name="repeat-%i.name"' % i in output
@@ -170,9 +160,9 @@
         assert 'name="repeat-%i.comment"' % i in output
         assert 'id="form_repeat_%i_comment"' % i in output
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test_value")
-    output = cherrypy.response.body[0].lower()
+    testutil.mount(MyRoot())
+    response = testutil.go("/test_value")
+    output = response.body.lower()
     name_p = 'name="repeat-0.name"'
     value_p = 'value="foo"'
     assert (re.compile('.*'.join([value_p, name_p])).search(output) or
@@ -204,11 +194,11 @@
             re.compile('.*'.join([name_p, value_p])).search(output)
     )
 
-    cherrypy.root = MyRoot()
-    testutil.create_request("/test_validation?repeat-0.name=foo&repeat-1.name="
+    testutil.mount(MyRoot())
+    response = testutil.go("/test_validation?repeat-0.name=foo&repeat-1.name="
         "&repeat-2.name=bar&repeat-3.name=&repeat-4.name=")
-    output = cherrypy.response.body[0].lower()
-    validation_errors = cherrypy.request.validation_errors
+    output = response.body.lower()
+    validation_errors = response.raw['validation_errors']
     assert validation_errors.has_key("repeat")
     assert isinstance(validation_errors["repeat"], list)
     assert validation_errors["repeat"][0] is None
Index: turbogears/widgets/tests/test_widgets.py
===================================================================
--- turbogears/widgets/tests/test_widgets.py	(revision 4621)
+++ turbogears/widgets/tests/test_widgets.py	(working copy)
@@ -4,7 +4,7 @@
 from turbogears.testutil import catch_validation_errors
 
 def setup_module():
-    testutil.start_cp()
+    testutil.start_server()
     cherrypy.serving.request = testutil.DummyRequest()
 
 
Index: turbogears/widgets/tests/test_nested_widgets.py
===================================================================
--- turbogears/widgets/tests/test_nested_widgets.py	(revision 4621)
+++ turbogears/widgets/tests/test_nested_widgets.py	(working copy)
@@ -1,15 +1,13 @@
 import re
-from turbogears.testutil import catch_validation_errors, start_cp
+from turbogears.testutil import catch_validation_errors, start_server
 import turbogears
 from turbogears import testutil
-import cherrypy
 import turbogears.widgets as widgets
 import turbogears.validators as validators
 from turbogears.widgets.meta import copy_schema
 
 def setup_module():
-    global oldrequest
-    start_cp()
+    start_server()
 
 
 #XXX: We ignore missing keys to make passing value easier in tests
@@ -56,16 +54,16 @@
         Checks if names fo the widgets are set correctly depending on their
         path.
         """
-        cherrypy.root = self.MyRoot()
-        testutil.create_request('/?test_id=sub2')
-        output = cherrypy.response.body[0]
+        testutil.mount(self.MyRoot())
+        response = testutil.go('/?test_id=sub2')
+        output = response.body
         value_p = 'value="22"'
         name_p = 'name="sub.sub2.age"'
         assert (re.compile('.*'.join([value_p, name_p])).search(output) or
                 re.compile('.*'.join([name_p, value_p])).search(output))
 
-        testutil.create_request('/?test_id=sub')
-        output = cherrypy.response.body[0]
+        response = testutil.go('/?test_id=sub')
+        output = response.body
         value_p = 'value="22"'
         name_p = 'name="sub.age"'
         id_p = 'id="myform_sub_age"'
@@ -74,8 +72,8 @@
         assert (re.compile('.*'.join([value_p, id_p])).search(output) or
                 re.compile('.*'.join([id_p, value_p])).search(output))
 
-        testutil.create_request('/?test_id=age')
-        output = cherrypy.response.body[0]
+        response = testutil.go('/?test_id=age')
+        output = response.body
         value_p = 'value="22"'
         name_p = 'name="age"'
         assert (re.compile('.*'.join([value_p, name_p])).search(output) or
Index: turbogears/view/base.py
===================================================================
--- turbogears/view/base.py	(revision 4621)
+++ turbogears/view/base.py	(working copy)
@@ -107,6 +107,10 @@
     @type template: string
 
     """
+    environ = getattr(cherrypy.request, 'wsgi_environ', {})
+    if environ.get('paste.testing', False):
+        cherrypy.request.wsgi_environ['paste.testing_variables']['raw'] = info
+
     template = info.pop("tg_template", template)
     if not info.has_key("tg_flash"):
         if config.get("tg.empty_flash", True):

