Testing Applications with Paste
+++++++++++++++++++++++++++++++
:author: Ian Bicking <ianb@colorstudy.com>
:revision: $Rev$
:date: $LastChangedDate$
.. 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://groups.google.com/group/paste-users>`_.
These facilities let you test your Paste and WSGI-based applications
easily and without a server.
.. include:: include/contact.txt
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
================
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``.