Testing Applications with Paste

author:Ian Bicking <ianb@colorstudy.com>


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.

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

If you have questions about this document, please contact the paste mailing list or try IRC (#pythonpaste on freenode.net). If there’s something that confused you and you want to give feedback, please submit an issue.

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
    # 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.
Returns a list of all matching headers.
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.
Raises an error if any of the strings are not found in the response.
Opens the HTML response in a browser; useful for debugging.
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)
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 for more on the form objects.

Request objects:

The url requested.
The environment used for the request.
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:
# Or submit a button:

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.