diff --git a/product/ERP5Type/tests/ERP5TypeTestCase.py b/product/ERP5Type/tests/ERP5TypeTestCase.py
index 89c36ee958d565215fb7a90a6c7c020279f0e831..796ecdaa1f591958e2ec52287b4341aacead1e60 100644
--- a/product/ERP5Type/tests/ERP5TypeTestCase.py
+++ b/product/ERP5Type/tests/ERP5TypeTestCase.py
@@ -163,6 +163,8 @@ import os
 from cStringIO import StringIO
 from urllib import urlretrieve
 from glob import glob
+import sys, re, base64
+import transaction
 
 portal_name = 'erp5_portal'
 
@@ -790,6 +792,100 @@ class ERP5TypeTestCase(PortalTestCase):
                             % title) # run_unit_test depends on this string.
         raise
 
+    def publish(self, path, basic=None, env=None, extra=None,
+                request_method='GET', stdin=None, handle_errors=True):
+        '''Publishes the object at 'path' returning a response object.'''
+
+        from StringIO import StringIO
+        from ZPublisher.Response import Response
+        from ZPublisher.Test import publish_module
+
+        from AccessControl.SecurityManagement import getSecurityManager
+        from AccessControl.SecurityManagement import setSecurityManager
+        
+        # Save current security manager
+        sm = getSecurityManager()
+
+        # Commit the sandbox for good measure
+        transaction.commit()
+
+        if env is None:
+            env = {}
+        if extra is None:
+            extra = {}
+
+        request = self.app.REQUEST
+
+        env['SERVER_NAME'] = request['SERVER_NAME']
+        env['SERVER_PORT'] = request['SERVER_PORT']
+        env['REQUEST_METHOD'] = request_method
+
+        p = path.split('?')
+        if len(p) == 1:
+            env['PATH_INFO'] = p[0]
+        elif len(p) == 2:
+            [env['PATH_INFO'], env['QUERY_STRING']] = p
+        else:
+            raise TypeError, ''
+
+        if basic:
+            env['HTTP_AUTHORIZATION'] = "Basic %s" % base64.encodestring(basic).replace('\012', '')
+
+        if stdin is None:
+            stdin = StringIO()
+
+        outstream = StringIO()
+        response = Response(stdout=outstream, stderr=sys.stderr)
+
+        publish_module('Zope2',
+                       response=response,
+                       stdin=stdin,
+                       environ=env,
+                       extra=extra,
+                       debug=not handle_errors,
+                      )
+
+        # Restore security manager
+        setSecurityManager(sm)
+
+        return ResponseWrapper(response, outstream, path)
+
+
+class ResponseWrapper:
+    '''Decorates a response object with additional introspective methods.'''
+
+    _bodyre = re.compile('^$^\n(.*)', re.MULTILINE | re.DOTALL)
+
+    def __init__(self, response, outstream, path):
+        self._response = response
+        self._outstream = outstream
+        self._path = path
+
+    def __getattr__(self, name):
+        return getattr(self._response, name)
+
+    def getOutput(self):
+        '''Returns the complete output, headers and all.'''
+        return self._outstream.getvalue()
+
+    def getBody(self):
+        '''Returns the page body, i.e. the output par headers.'''
+        body = self._bodyre.search(self.getOutput())
+        if body is not None:
+            body = body.group(1)
+        return body
+
+    def getPath(self):
+        '''Returns the path used by the request.'''
+        return self._path
+
+    def getHeader(self, name):
+        '''Returns the value of a response header.'''
+        return self.headers.get(name.lower())
+
+    def getCookie(self, name):
+        '''Returns a response cookie.'''
+        return self.cookies.get(name)
 
 class ERP5ReportTestCase(ERP5TypeTestCase):
   """Base class for testing ERP5 Reports