1Testing Applications with Paste 2+++++++++++++++++++++++++++++++ 3 4:author: Ian Bicking <ianb@colorstudy.com> 5:revision: $Rev$ 6:date: $LastChangedDate$ 7 8.. contents:: 9 10Introduction 11============ 12 13Paste includes functionality for testing your application in a 14convenient manner. These facilities are quite young, and feedback is 15invited. Feedback and discussion should take place on the 16`Paste-users list 17<http://groups.google.com/group/paste-users>`_. 18 19These facilities let you test your Paste and WSGI-based applications 20easily and without a server. 21 22.. include:: include/contact.txt 23 24The Tests Themselves 25==================== 26 27The ``app`` object is a wrapper around your application, with many 28methods 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 42The methods of the ``app`` object (a ``paste.tests.fixture.TestApp`` 43object): 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 66The 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 105Request 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 116Form Submissions 117================ 118 119You can fill out and submit forms from your tests. First you get the 120form:: 121 122 res = testapp.get('/entry_form') 123 form = res.forms[0] 124 125Then 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 132Finally 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 139Framework Hooks 140=============== 141 142Frameworks can detect that they are in a testing environment by the 143presence (and truth) of the WSGI environmental variable 144``"paste.testing"``. 145 146More generally, frameworks can detect that something (possibly a test 147fixture) is ready to catch unexpected errors by the presence and truth 148of ``"paste.throw_errors"`` (this is sometimes set outside of testing 149fixtures too, when an error-handling middleware is in place). 150 151Frameworks that want to expose the inner structure of the request may 152use ``"paste.testing_variables"``. This will be a dictionary -- any 153values put into that dictionary will become attributes of the response 154object. So if you do ``env["paste.testing_variables"]['template'] = 155template_name`` in your framework, then ``response.template`` will be 156``template_name``. 157