Home | History | Annotate | Download | only in docs
      1 Testing Applications with Paste
      2 +++++++++++++++++++++++++++++++
      3 
      4 :author: Ian Bicking <ianb (a] colorstudy.com>
      5 :revision: $Rev$
      6 :date: $LastChangedDate$
      7 
      8 .. contents::
      9 
     10 Introduction
     11 ============
     12 
     13 Paste includes functionality for testing your application in a
     14 convenient manner.  These facilities are quite young, and feedback is
     15 invited.  Feedback and discussion should take place on the
     16 `Paste-users list
     17 <http://groups.google.com/group/paste-users>`_.
     18 
     19 These facilities let you test your Paste and WSGI-based applications
     20 easily and without a server. 
     21 
     22 .. include:: include/contact.txt
     23 
     24 The Tests Themselves
     25 ====================
     26 
     27 The ``app`` object is a wrapper around your application, with many
     28 methods to make testing convenient.  Here's an example test script::
     29 
     30     def test_myapp():
     31         res = app.get('/view', params={'id': 10})
     32         # We just got /view?id=10
     33         res.mustcontain('Item 10')
     34         res = app.post('/view', params={'id': 10, 'name': 'New item
     35             name'})
     36         # The app does POST-and-redirect...
     37         res = res.follow()
     38         assert res.request.url == '/view?id=10'
     39         res.mustcontain('New item name')
     40         res.mustcontain('Item updated')
     41 
     42 The methods of the ``app`` object (a ``paste.tests.fixture.TestApp``
     43 object):
     44 
     45 ``get(url, params={}, headers={}, status=None)``:
     46     Gets the URL.  URLs are based in the root of your application; no
     47     domains are allowed.  Parameters can be given as a dictionary, or
     48     included directly in the ``url``.  Headers can also be added.
     49 
     50     This tests that the status is a ``200 OK`` or a redirect header,
     51     unless you pass in a ``status``.  A status of ``"*"`` will never
     52     fail; or you can assert a specific status (like ``500``).
     53 
     54     Also, if any errors are written to the error stream this will
     55     raise an error.
     56 
     57 ``post(url, params={}, headers={}, status=None, upload_files=())``:
     58     POSTS to the URL.  Like GET, except also allows for uploading
     59     files.  The uploaded files are a list of ``(field_name, filename,
     60     file_content)``.  
     61 
     62     If you don't want to do a urlencoded post body, you can put a
     63     ``content-type`` header in your header, and pass the body in as a
     64     string with ``params``.
     65 
     66 The response object:
     67 
     68 ``header(header_name, [default])``:
     69     Returns the named header.  It's an error if there is more than one
     70     matching header.  If you don't provide a default, it is an error
     71     if there is no matching header.
     72 
     73 ``all_headers(header_name):``
     74     Returns a list of all matching headers.
     75 
     76 ``follow(**kw)``:
     77     Follows the redirect, returning the new response.  It is an error
     78     if this response wasn't a redirect.  Any keyword arguments are
     79     passed to ``app.get`` (e.g., ``status``).
     80 
     81 ``x in res``:
     82     Returns True if the string is found in the response.  Whitespace
     83     is normalized for this test.
     84 
     85 ``mustcontain(*strings)``:
     86     Raises an error if any of the strings are not found in the
     87     response.
     88 
     89 ``showbrowser()``:
     90     Opens the HTML response in a browser; useful for debugging.
     91 
     92 ``str(res)``:
     93     Gives a slightly-compacted version of the response.
     94 
     95 ``click(description=None, linkid=None, href=None, anchor=None, index=None, verbose=False)``: 
     96     Clicks the described link (`see docstring for more
     97     <./class-paste.fixture.TestResponse.html#click>`_)
     98 
     99 ``forms``:
    100     Return a dictionary of forms; you can use both indexes (refer to
    101     the forms in order) or the string ids of forms (if you've given
    102     them ids) to identify the form.  See `Form Submissions <#form-submissions>`_ for
    103     more on the form objects.
    104 
    105 Request objects:
    106 
    107 ``url``:
    108     The url requested.
    109 
    110 ``environ``:
    111     The environment used for the request.
    112 
    113 ``full_url``:
    114     The url with query string.
    115 
    116 Form Submissions
    117 ================
    118 
    119 You can fill out and submit forms from your tests.  First you get the
    120 form::
    121 
    122     res = testapp.get('/entry_form')
    123     form = res.forms[0]
    124 
    125 Then you fill it in fields::
    126 
    127     # when there's one unambiguous name field:
    128     form['name'] = 'Bob'
    129     # Enter something into the first field named 'age'
    130     form.set('age', '45', index=1)
    131 
    132 Finally you submit::
    133 
    134     # Submit with no particular submit button pressed:
    135     form.submit()
    136     # Or submit a button:
    137     form.submit('submit_button_name')
    138 
    139 Framework Hooks
    140 ===============
    141 
    142 Frameworks can detect that they are in a testing environment by the
    143 presence (and truth) of the WSGI environmental variable
    144 ``"paste.testing"``.
    145 
    146 More generally, frameworks can detect that something (possibly a test
    147 fixture) is ready to catch unexpected errors by the presence and truth
    148 of ``"paste.throw_errors"`` (this is sometimes set outside of testing
    149 fixtures too, when an error-handling middleware is in place).
    150 
    151 Frameworks that want to expose the inner structure of the request may
    152 use ``"paste.testing_variables"``.  This will be a dictionary -- any
    153 values put into that dictionary will become attributes of the response
    154 object.  So if you do ``env["paste.testing_variables"]['template'] =
    155 template_name`` in your framework, then ``response.template`` will be
    156 ``template_name``.
    157