WebTest

repository·main·Indexed 43 days ago

https://github.com/pylons/webtest

A testing library for WSGI applications that enables full-stack functional, integration, and acceptance testing by simulating HTTP requests without requiring a running HTTP server. It provides the TestApp class for simulating requests, TestResponse for inspecting results, and specialized tools for interacting with HTML forms, JSON APIs, and file uploads. Version 3.0.8.dev0.

Tokens
7.8K
Snippets
26
Records
40
Agent score
55%

What's inside WebTest

  1. Overview of WebTest

    main
    WebTest is a library designed to wrap any WSGI application, allowing you to send test requests directly to the application without the need to start a live HTTP server. It provides a convenient way to perform full-stack testing for applications built with any WSGI-compatible framework.
  2. What is WebTest and how does it work?

    main

    WebTest is a library for functional, integration, or acceptance testing of WSGI-based web applications.

    Instead of starting a live HTTP server, WebTest wraps your WSGI application and simulates how a WSGI HTTP server would call it. This allows you to test the full stack of your application (including template logic and web routing) without the overhead of a real network server. It is compatible with almost any Python web framework that supports the WSGI interface.

  3. Detect testing environments using WSGI environment variables

    main
    Web frameworks can identify that they are running within a WebTest environment by checking for the presence and truthiness of the WSGI environment variable paste.testing. This variable is inherited from paste.fixture and is used to signal that the application is being tested.
  4. Expose request internals via paste.testing_variables

    main

    Frameworks can expose internal request data (like template names or metadata) to the WebTest response object by using the paste.testing_variables WSGI environment variable.

    Any key-value pairs added to the dictionary assigned to paste.testing_variables will be attached as attributes to the resulting response object. For example, setting env['paste.testing_variables']['template'] = 'index.html' allows you to access the value via response.template in your test assertions.

    # Inside the framework/app logic:
    env['paste.testing_variables']['template'] = 'index.html'
    
    # Inside the WebTest test:
    assert response.template == 'index.html'
  5. Understand the TestResponse object

    main

    The TestResponse object is the primary way to inspect the results of a request made via TestApp. It is based on webob.response.Response but includes additional methods and attributes specifically designed for testing, such as content assertion, link clicking, and automated redirect following.

    from webtest.app import TestApp
    from webob import Request, Response
    
    def application(environ, start_response):
        return Response('Hello World')
    
    app = TestApp(application)
    res = app.get('/')
    # res is a TestResponse object
  6. Handle unexpected errors in testing using paste.throw_errors

    main
    To signal that a test fixture or environment is ready to catch unexpected errors, frameworks can check for the presence and truthiness of the paste.throw_errors WSGI environment variable. This is useful when error-handling middleware is active and you want to ensure errors are propagated to the test runner.
  7. Install WebTest

    main

    You can install the latest stable release of WebTest using pip or easy_install. If you require the development version, you can install directly from the source tarball.

    $ pip install WebTest
    $ easy_install WebTest
    
    # For the development version:
    $ pip install https://nodeload.github.com/Pylons/webtest/tar.gz/main
  8. Submit a form

    main

    Once the form is filled, use the .submit() method to send the request. You can submit the form generally, or target a specific submit button.

    • General Submit: form.submit()
    • By Name: form.submit('submit_button_name')
    • By Index: form.submit('submit_button_name', index=1)
    • By Value: form.submit('submit_button_name', value='Submit 2')
    # Submit without a specific button
    res = form.submit()
    
    # Submit a specific button by name
    res = form.submit('submit')
    
    # Submit a specific button by name and index
    res = form.submit('submit', index=1)
    
    # Submit a specific button by name and value
    res = form.submit('submit', value="Submit 2")
  9. Quick start with TestApp

    main

    The core of WebTest is the TestApp class, which acts as a wrapper for your WSGI application. You instantiate TestApp with your application object, and then use it to perform HTTP requests like get(), post(), etc. The resulting response object allows you to inspect status codes, headers, and body content.

    from webtest import TestApp
    
    # 1. Define or import your WSGI application
    def application(environ, start_response):
        headers = [('Content-Type', 'text/html; charset=utf8')]
        start_response('200 OK', headers)
        return [b'<html><body>form</body></html>']
    
    # 2. Wrap the application in TestApp
    app = TestApp(application)
    
    # 3. Perform an HTTP GET request
    resp = app.get('/')
    
    # 4. Verify the results
    assert resp.status == '200 OK'
    assert resp.status_int == 200
    assert resp.content_type == 'text/html'
    resp.mustcontain('<html>')
  10. Upload files in a form

    main

    To simulate file uploads, use the webtest.Upload class. You can provide the filename, the raw bytes, and the content type.

    • For a single file: form['file'] = Upload('path/to/file').
    • For multiple files (where the input has a multiple attribute): Pass a list of Upload objects.
    from webtest import Upload
    
    # Single file
    form['file'] = Upload('README.rst')
    form['file'] = Upload('README.rst', b'data', 'text/x-rst')
    
    # Multiple files
    form['files'] = [
        Upload('README.rst'),
        Upload('LICENSE.rst'),
    ]
  11. Handle select and multiple select fields

    main

    Select fields can be manipulated by value or by the visible text of the options.

    • Simple Select: Assign the value directly: form['select'] = 'option1'.
    • Multiple Select: Assign a list of values: form['multiple'] = ['option1', 'option2'].
    • By Text: Use .select(text="...") for single selects or .select_multiple(texts=["..."]) for multiple selects.
    • Bypassing Validation: If you need to set a value that is not present in the <option> tags, use .force_value().
    # Simple select
    form['select'] = 'option1'
    form['select'].select(text="Option 2")
    
    # Multiple select
    form['multiple'] = ['option1']
    form['multiple'].select_multiple(texts=["Option 1", "Option 2"])
    
    # Force invalid values
    form['select'].force_value(['optionX'])
  12. Simulate authentication and modify the environment

    main

    You can simulate authenticated users or specific environment states using extra_environ or the .authorization property.

    Simulating REMOTE_USER

    If your app checks environ['REMOTE_USER'], you can set it per request or globally:

    • Per request: app.get('/secret', extra_environ=dict(REMOTE_USER='bob'))
    • Globally: app = TestApp(my_app, extra_environ=dict(REMOTE_USER='bob'))

    Using HTTP Authorization

    Set the .authorization property on the TestApp instance to set the HTTP_AUTHORIZATION header. Supported formats include:

    • Basic: app.authorization = ('Basic', ('user', 'password'))
    • Bearer: app.authorization = ('Bearer', 'mytoken')
    • JWT: app.authorization = ('JWT', 'myjwt')
    app.get('/secret', extra_environ=dict(REMOTE_USER='bob'))
    
    app = TestApp(my_app)
    app.authorization = ('Basic', ('user', 'password'))
    app.authorization = ('Bearer', 'mytoken')