Ticket #9002: t9002.diff

File t9002.diff, 17.7 KB (added by Russell Keith-Magee, 14 years ago)

Implemenation of RequestFactory as base class for Client

  • django/test/__init__.py

    diff -r e00354919e8e django/test/__init__.py
    a b  
    22Django Unit Test and Doctest framework.
    33"""
    44
    5 from django.test.client import Client
     5from django.test.client import Client, RequestFactory
    66from django.test.testcases import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
    77from django.test.utils import Approximate
  • django/test/client.py

    diff -r e00354919e8e django/test/client.py
    a b  
    156156        file.read()
    157157    ]
    158158
    159 class Client(object):
     159
     160
     161class RequestFactory(object):
    160162    """
    161     A class that can act as a client for testing purposes.
     163    Class that lets you create mock Request objects for use in testing.
    162164
    163     It allows the user to compose GET and POST requests, and
    164     obtain the response that the server gave to those requests.
    165     The server Response objects are annotated with the details
    166     of the contexts and templates that were rendered during the
    167     process of serving the request.
     165    Usage:
    168166
    169     Client objects are stateful - they will retain cookie (and
    170     thus session) details for the lifetime of the Client instance.
     167    rf = RequestFactory()
     168    get_request = rf.get('/hello/')
     169    post_request = rf.post('/submit/', {'foo': 'bar'})
    171170
    172     This is not intended as a replacement for Twill/Selenium or
    173     the like - it is here to allow testing against the
    174     contexts and templates produced by a view, rather than the
    175     HTML rendered to the end-user.
     171    Once you have a request object you can pass it to any view function,
     172    just as if that view had been hooked up using a URLconf.
    176173    """
    177     def __init__(self, enforce_csrf_checks=False, **defaults):
    178         self.handler = ClientHandler(enforce_csrf_checks)
     174    def __init__(self, **defaults):
    179175        self.defaults = defaults
    180176        self.cookies = SimpleCookie()
    181         self.exc_info = None
    182177        self.errors = StringIO()
    183178
    184     def store_exc_info(self, **kwargs):
     179    def _base_environ(self, **request):
    185180        """
    186         Stores exceptions when they are generated by a view.
    187         """
    188         self.exc_info = sys.exc_info()
    189 
    190     def _session(self):
    191         """
    192         Obtains the current session variables.
    193         """
    194         if 'django.contrib.sessions' in settings.INSTALLED_APPS:
    195             engine = import_module(settings.SESSION_ENGINE)
    196             cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
    197             if cookie:
    198                 return engine.SessionStore(cookie.value)
    199         return {}
    200     session = property(_session)
    201 
    202     def request(self, **request):
    203         """
    204         The master request method. Composes the environment dictionary
    205         and passes to the handler, returning the result of the handler.
    206         Assumes defaults for the query environment, which can be overridden
    207         using the arguments to the request.
     181        The base environment for a request.
    208182        """
    209183        environ = {
    210184            'HTTP_COOKIE':       self.cookies.output(header='', sep='; '),
     
    225199        }
    226200        environ.update(self.defaults)
    227201        environ.update(request)
     202        return environ
     203
     204    def request(self, **request):
     205        "Construct a generic request object."
     206        return WSGIRequest(self._base_environ(**request))
     207
     208    def get(self, path, data={}, **extra):
     209        "Construct a GET request"
     210
     211        parsed = urlparse(path)
     212        r = {
     213            'CONTENT_TYPE':    'text/html; charset=utf-8',
     214            'PATH_INFO':       urllib.unquote(parsed[2]),
     215            'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
     216            'REQUEST_METHOD': 'GET',
     217            'wsgi.input':      FakePayload('')
     218        }
     219        r.update(extra)
     220        return self.request(**r)
     221
     222    def post(self, path, data={}, content_type=MULTIPART_CONTENT,
     223             **extra):
     224        "Construct a POST request."
     225
     226        if content_type is MULTIPART_CONTENT:
     227            post_data = encode_multipart(BOUNDARY, data)
     228        else:
     229            # Encode the content so that the byte representation is correct.
     230            match = CONTENT_TYPE_RE.match(content_type)
     231            if match:
     232                charset = match.group(1)
     233            else:
     234                charset = settings.DEFAULT_CHARSET
     235            post_data = smart_str(data, encoding=charset)
     236
     237        parsed = urlparse(path)
     238        r = {
     239            'CONTENT_LENGTH': len(post_data),
     240            'CONTENT_TYPE':   content_type,
     241            'PATH_INFO':      urllib.unquote(parsed[2]),
     242            'QUERY_STRING':   parsed[4],
     243            'REQUEST_METHOD': 'POST',
     244            'wsgi.input':     FakePayload(post_data),
     245        }
     246        r.update(extra)
     247        return self.request(**r)
     248
     249    def head(self, path, data={}, **extra):
     250        "Construct a HEAD request."
     251
     252        parsed = urlparse(path)
     253        r = {
     254            'CONTENT_TYPE':    'text/html; charset=utf-8',
     255            'PATH_INFO':       urllib.unquote(parsed[2]),
     256            'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
     257            'REQUEST_METHOD': 'HEAD',
     258            'wsgi.input':      FakePayload('')
     259        }
     260        r.update(extra)
     261        return self.request(**r)
     262
     263    def options(self, path, data={}, **extra):
     264        "Constrict an OPTIONS request"
     265
     266        parsed = urlparse(path)
     267        r = {
     268            'PATH_INFO':       urllib.unquote(parsed[2]),
     269            'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
     270            'REQUEST_METHOD': 'OPTIONS',
     271            'wsgi.input':      FakePayload('')
     272        }
     273        r.update(extra)
     274        return self.request(**r)
     275
     276    def put(self, path, data={}, content_type=MULTIPART_CONTENT,
     277            **extra):
     278        "Construct a PUT request."
     279
     280        if content_type is MULTIPART_CONTENT:
     281            post_data = encode_multipart(BOUNDARY, data)
     282        else:
     283            post_data = data
     284
     285        # Make `data` into a querystring only if it's not already a string. If
     286        # it is a string, we'll assume that the caller has already encoded it.
     287        query_string = None
     288        if not isinstance(data, basestring):
     289            query_string = urlencode(data, doseq=True)
     290
     291        parsed = urlparse(path)
     292        r = {
     293            'CONTENT_LENGTH': len(post_data),
     294            'CONTENT_TYPE':   content_type,
     295            'PATH_INFO':      urllib.unquote(parsed[2]),
     296            'QUERY_STRING':   query_string or parsed[4],
     297            'REQUEST_METHOD': 'PUT',
     298            'wsgi.input':     FakePayload(post_data),
     299        }
     300        r.update(extra)
     301        return self.request(**r)
     302
     303    def delete(self, path, data={}, **extra):
     304        "Construct a DELETE request."
     305
     306        parsed = urlparse(path)
     307        r = {
     308            'PATH_INFO':       urllib.unquote(parsed[2]),
     309            'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
     310            'REQUEST_METHOD': 'DELETE',
     311            'wsgi.input':      FakePayload('')
     312        }
     313        r.update(extra)
     314        return self.request(**r)
     315
     316
     317class Client(RequestFactory):
     318    """
     319    A class that can act as a client for testing purposes.
     320
     321    It allows the user to compose GET and POST requests, and
     322    obtain the response that the server gave to those requests.
     323    The server Response objects are annotated with the details
     324    of the contexts and templates that were rendered during the
     325    process of serving the request.
     326
     327    Client objects are stateful - they will retain cookie (and
     328    thus session) details for the lifetime of the Client instance.
     329
     330    This is not intended as a replacement for Twill/Selenium or
     331    the like - it is here to allow testing against the
     332    contexts and templates produced by a view, rather than the
     333    HTML rendered to the end-user.
     334    """
     335    def __init__(self, enforce_csrf_checks=False, **defaults):
     336        super(Client, self).__init__(**defaults)
     337        self.handler = ClientHandler(enforce_csrf_checks)
     338        self.exc_info = None
     339
     340    def store_exc_info(self, **kwargs):
     341        """
     342        Stores exceptions when they are generated by a view.
     343        """
     344        self.exc_info = sys.exc_info()
     345
     346    def _session(self):
     347        """
     348        Obtains the current session variables.
     349        """
     350        if 'django.contrib.sessions' in settings.INSTALLED_APPS:
     351            engine = import_module(settings.SESSION_ENGINE)
     352            cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
     353            if cookie:
     354                return engine.SessionStore(cookie.value)
     355        return {}
     356    session = property(_session)
     357
     358
     359    def request(self, **request):
     360        """
     361        The master request method. Composes the environment dictionary
     362        and passes to the handler, returning the result of the handler.
     363        Assumes defaults for the query environment, which can be overridden
     364        using the arguments to the request.
     365        """
     366        environ = self._base_environ(**request)
    228367
    229368        # Curry a data dictionary into an instance of the template renderer
    230369        # callback function.
     
    290429            signals.template_rendered.disconnect(dispatch_uid="template-render")
    291430            got_request_exception.disconnect(dispatch_uid="request-exception")
    292431
    293 
    294432    def get(self, path, data={}, follow=False, **extra):
    295433        """
    296434        Requests a response from the server using GET.
    297435        """
    298         parsed = urlparse(path)
    299         r = {
    300             'CONTENT_TYPE':    'text/html; charset=utf-8',
    301             'PATH_INFO':       urllib.unquote(parsed[2]),
    302             'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
    303             'REQUEST_METHOD': 'GET',
    304             'wsgi.input':      FakePayload('')
    305         }
    306         r.update(extra)
    307 
    308         response = self.request(**r)
     436        response = super(Client, self).get(path, data=data, **extra)
    309437        if follow:
    310438            response = self._handle_redirects(response, **extra)
    311439        return response
     
    315443        """
    316444        Requests a response from the server using POST.
    317445        """
    318         if content_type is MULTIPART_CONTENT:
    319             post_data = encode_multipart(BOUNDARY, data)
    320         else:
    321             # Encode the content so that the byte representation is correct.
    322             match = CONTENT_TYPE_RE.match(content_type)
    323             if match:
    324                 charset = match.group(1)
    325             else:
    326                 charset = settings.DEFAULT_CHARSET
    327             post_data = smart_str(data, encoding=charset)
    328 
    329         parsed = urlparse(path)
    330         r = {
    331             'CONTENT_LENGTH': len(post_data),
    332             'CONTENT_TYPE':   content_type,
    333             'PATH_INFO':      urllib.unquote(parsed[2]),
    334             'QUERY_STRING':   parsed[4],
    335             'REQUEST_METHOD': 'POST',
    336             'wsgi.input':     FakePayload(post_data),
    337         }
    338         r.update(extra)
    339 
    340         response = self.request(**r)
     446        response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
    341447        if follow:
    342448            response = self._handle_redirects(response, **extra)
    343449        return response
     
    346452        """
    347453        Request a response from the server using HEAD.
    348454        """
    349         parsed = urlparse(path)
    350         r = {
    351             'CONTENT_TYPE':    'text/html; charset=utf-8',
    352             'PATH_INFO':       urllib.unquote(parsed[2]),
    353             'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
    354             'REQUEST_METHOD': 'HEAD',
    355             'wsgi.input':      FakePayload('')
    356         }
    357         r.update(extra)
    358 
    359         response = self.request(**r)
     455        response = super(Client, self).head(path, data=data, **extra)
    360456        if follow:
    361457            response = self._handle_redirects(response, **extra)
    362458        return response
     
    365461        """
    366462        Request a response from the server using OPTIONS.
    367463        """
    368         parsed = urlparse(path)
    369         r = {
    370             'PATH_INFO':       urllib.unquote(parsed[2]),
    371             'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
    372             'REQUEST_METHOD': 'OPTIONS',
    373             'wsgi.input':      FakePayload('')
    374         }
    375         r.update(extra)
    376 
    377         response = self.request(**r)
     464        response = super(Client, self).options(path, data=data, **extra)
    378465        if follow:
    379466            response = self._handle_redirects(response, **extra)
    380467        return response
     
    384471        """
    385472        Send a resource to the server using PUT.
    386473        """
    387         if content_type is MULTIPART_CONTENT:
    388             post_data = encode_multipart(BOUNDARY, data)
    389         else:
    390             post_data = data
    391 
    392         # Make `data` into a querystring only if it's not already a string. If
    393         # it is a string, we'll assume that the caller has already encoded it.
    394         query_string = None
    395         if not isinstance(data, basestring):
    396             query_string = urlencode(data, doseq=True)
    397 
    398         parsed = urlparse(path)
    399         r = {
    400             'CONTENT_LENGTH': len(post_data),
    401             'CONTENT_TYPE':   content_type,
    402             'PATH_INFO':      urllib.unquote(parsed[2]),
    403             'QUERY_STRING':   query_string or parsed[4],
    404             'REQUEST_METHOD': 'PUT',
    405             'wsgi.input':     FakePayload(post_data),
    406         }
    407         r.update(extra)
    408 
    409         response = self.request(**r)
     474        response = super(Client, self).put(path, data=data, content_type=content_type, **extra)
    410475        if follow:
    411476            response = self._handle_redirects(response, **extra)
    412477        return response
     
    415480        """
    416481        Send a DELETE request to the server.
    417482        """
    418         parsed = urlparse(path)
    419         r = {
    420             'PATH_INFO':       urllib.unquote(parsed[2]),
    421             'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
    422             'REQUEST_METHOD': 'DELETE',
    423             'wsgi.input':      FakePayload('')
    424         }
    425         r.update(extra)
    426 
    427         response = self.request(**r)
     483        response = super(Client, self).delete(path, data=data, **extra)
    428484        if follow:
    429485            response = self._handle_redirects(response, **extra)
    430486        return response
    431487
    432488    def login(self, **credentials):
    433489        """
    434         Sets the Client to appear as if it has successfully logged into a site.
     490        Sets the Factory to appear as if it has successfully logged into a site.
    435491
    436492        Returns True if login is possible; False if the provided credentials
    437493        are incorrect, or the user is inactive, or if the sessions framework is
     
    506562            if response.redirect_chain[-1] in response.redirect_chain[0:-1]:
    507563                break
    508564        return response
    509 
  • docs/topics/testing.txt

    diff -r e00354919e8e docs/topics/testing.txt
    a b  
    10141014            # Check that the rendered context contains 5 customers.
    10151015            self.assertEqual(len(response.context['customers']), 5)
    10161016
     1017The request factory
     1018-------------------
     1019
     1020.. Class:: RequestFactory
     1021
     1022The :class:`~django.test.client.RequestFactory` is a simplified version of the
     1023test client that provides a simple way to generate a request that can be used
     1024as the first argument to any view. This means you can test a view function
     1025the same way as you would test any other function -- as a black box, with
     1026exactly known inputs, testing for specific outputs.
     1027
     1028The API for the :class:`~django.test.client.RequestFactory` is a slightly
     1029restricted subset of the test client API:
     1030
     1031    * It only has the methods that correspond to HTTP method types:
     1032        ``get()``, ``post()``, ``put()``, ``delete()``, ``head()`` and
     1033        ``options()``.
     1034
     1035    * These methods don't accept the ``follows`` argument. Since this is just
     1036      a factory for producing requests, it's up to you to handle the response.
     1037
     1038Example
     1039~~~~~~~
     1040
     1041The following is a simple unit test using the request factory::
     1042
     1043    from django.utils import unittest
     1044    from django.test.client import RequestFactory
     1045
     1046    class SimpleTest(unittest.TestCase):
     1047        def setUp(self):
     1048            # Every test needs a client.
     1049            self.factory = RequestFactory()
     1050
     1051        def test_details(self):
     1052            # Issue a GET request.
     1053            request = self.factory.get('/customer/details')
     1054
     1055            # Test my_view as if it were deployed at /customer/details
     1056            response = my_view(request)
     1057            self.assertEquals(response.status_code, 200)
     1058
    10171059TestCase
    10181060--------
    10191061
  • tests/modeltests/test_client/models.py

    diff -r e00354919e8e tests/modeltests/test_client/models.py
    a b  
    2020rather than the HTML rendered to the end-user.
    2121
    2222"""
    23 from django.test import Client, TestCase
     23from django.http import HttpResponse
    2424from django.conf import settings
    2525from django.core import mail
     26from django.test import Client, TestCase, RequestFactory
     27
     28from views import get_view
     29
    2630
    2731class ClientTest(TestCase):
    2832    fixtures = ['testdata.json']
     
    469473        """A test case can specify a custom class for self.client."""
    470474        self.assertEqual(hasattr(self.client, "i_am_customized"), True)
    471475
     476
     477class RequestFactoryTest(TestCase):
     478
     479    def test_request_factory(self):
     480        factory = RequestFactory()
     481        request = factory.get('/somewhere/')
     482        response = get_view(request)
     483
     484        self.assertTrue(isinstance(response, HttpResponse))
     485        self.assertEqual(response.status_code, 200)
     486        self.assertContains(response, 'This is a test')
Back to Top