Testing Applications with Paste
+++++++++++++++++++++++++++++++

:author: Ian Bicking <ianb@colorstudy.com>
:revision: $Rev: 5772 $
:date: $LastChangedDate: 2006-09-26 13:44:55 -0500 (Tue, 26 Sep 2006) $

.. contents::

Introduction
============

Paste includes functionality for testing your application in a
convenient manner.  These facilities are quite young, and feedback is
invited.  Feedback and discussion should take place on the
`Paste-users list
<http://pythonpaste.org/mailman/listinfo/paste-users>`_.

These facilities let you test your Paste and WSGI-based applications
easily and without a server. 

.. include:: include/contact.txt

The Test Environment
====================

This has been written with `py.test
<http://codespeak.net/py/current/doc/test.html>`_ in mind.  The
py.test convention is to put tests in modules named ``test_*.py``, and
the actual test functions are also named starting with ``test_``.

In your testing module you should do::

    from paste.tests.fixture import setup_module

This will add some initialization, will load your configure, will call
a ``reset_state`` function if you define one, and will insert the
variables ``app`` and ``CONFIG`` in your module.

If you have path problems you can set ``$PYTHONPATH``, or you can add
something to a ``conftest.py`` module, like::

    import sys
    sys.path.append('path/to/Paste', ...)

Note that paths given in the ``sys_path`` configuration item will also
be loaded (but Paste itself must be found first).

The Tests Themselves
====================

The ``app`` object is a wrapper around your application, with many
methods to make testing convenient.  Here's an example test script::

    def test_myapp():
        res = app.get('/view', params={'id': 10})
        # We just got /view?id=10
        res.mustcontain('Item 10')
        res = app.post('/view', params={'id': 10, 'name': 'New item
            name'})
        # The app does POST-and-redirect...
        res = res.follow()
        assert res.request.url == '/view?id=10'
        res.mustcontain('New item name')
        res.mustcontain('Item updated')

The methods of the ``app`` object (a ``paste.tests.fixture.TestApp``
object):

``get(url, params={}, headers={}, status=None)``:
    Gets the URL.  URLs are based in the root of your application; no
    domains are allowed.  Parameters can be given as a dictionary, or
    included directly in the ``url``.  Headers can also be added.

    This tests that the status is a ``200 OK`` or a redirect header,
    unless you pass in a ``status``.  A status of ``"*"`` will never
    fail; or you can assert a specific status (like ``500``).

    Also, if any errors are written to the error stream this will
    raise an error.

``post(url, params={}, headers={}, status=None, upload_files=())``:
    POSTS to the URL.  Like GET, except also allows for uploading
    files.  The uploaded files are a list of ``(field_name, filename,
    file_content)``.  

    If you don't want to do a urlencoded post body, you can put a
    ``content-type`` header in your header, and pass the body in as a
    string with ``params``.

The response object:

``header(header_name, [default])``:
    Returns the named header.  It's an error if there is more than one
    matching header.  If you don't provide a default, it is an error
    if there is no matching header.

``all_headers(header_name):``
    Returns a list of all matching headers.

``follow(**kw)``:
    Follows the redirect, returning the new response.  It is an error
    if this response wasn't a redirect.  Any keyword arguments are
    passed to ``app.get`` (e.g., ``status``).

``x in res``:
    Returns True if the string is found in the response.  Whitespace
    is normalized for this test.

``mustcontain(*strings)``:
    Raises an error if any of the strings are not found in the
    response.

``showbrowser()``:
    Opens the HTML response in a browser; useful for debugging.

``str(res)``:
    Gives a slightly-compacted version of the response.

``click(description=None, linkid=None, href=None, anchor=None, index=None, verbose=False)``: 
    Clicks the described link (`see docstring for more
    <./class-paste.fixture.TestResponse.html#click>`_)

``forms``:
    Return a dictionary of forms; you can use both indexes (refer to
    the forms in order) or the string ids of forms (if you've given
    them ids) to identify the form.  See `Form Submissions <#form-submissions>`_ for
    more on the form objects.

Request objects:

``url``:
    The url requested.

``environ``:
    The environment used for the request.

``full_url``:
    The url with query string.

Form Submissions
================

*New in svn trunk*

You can fill out and submit forms from your tests.  First you get the
form::

    res = testapp.get('/entry_form')
    form = res.forms[0]

Then you fill it in fields::

    # when there's one unambiguous name field:
    form['name'] = 'Bob'
    # Enter something into the first field named 'age'
    form.set('age', '45', index=1)

Finally you submit::

    # Submit with no particular submit button pressed:
    form.submit()
    # Or submit a button:
    form.submit('submit_button_name')

Framework Hooks
===============

Frameworks can detect that they are in a testing environment by the
presence (and truth) of the WSGI environmental variable
``"paste.testing"``.

More generally, frameworks can detect that something (possibly a test
fixture) is ready to catch unexpected errors by the presence and truth
of ``"paste.throw_errors"`` (this is sometimes set outside of testing
fixtures too, when an error-handling middleware is in place).

Frameworks that want to expose the inner structure of the request may
use ``"paste.testing_variables"``.  This will be a dictionary -- any
values put into that dictionary will become attributes of the response
object.  So if you do ``env["paste.testing_variables"]['template'] =
template_name`` in your framework, then ``response.template`` will be
``template_name``.
