Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 8401bf4f64
Fetching contributors…

Cannot retrieve contributors at this time

164 lines (123 sloc) 5.792 kB

Django Testing Example

This example uses:

  • mechanize to pretend to be a web browser
  • WSGI intercept to install a WSGI application in place of a real URI for testing
  • BeautifulSoup to parse the HTML fetched by the fake browser (substitute lxml or html5lib as you see fit)

This is based on Nathan Reynolds' Mechanize support for Django testcases and was developed by David Eyk in a public gist.

Implementation

Features file "features/browser.feature":

Feature: Demonstrate how to test Django with behave & mechanize

  Scenario: Logging in to our new Django site

    Given a user
    When I log in
    Then I see my account summary
     And I see a warm and welcoming message

Steps Python code "features/steps/browser.py":

from behave import given, when, then

@given('a user')
def impl(context):
    from django.contrib.auth.models import User
    u = User(username='foo', email='foo@example.com')
    u.set_password('bar')

@when('I log in')
def impl(context):
    br = context.browser
    br.open(context.browser_url('/account/login/'))
    br.select_form(nr=0)
    br.form['username'] = 'foo'
    br.form['password'] = 'bar'
    br.submit()

@then('I see my account summary')
def impl(context):
    br = context.browser
    response = br.response()
    assert response.code == 200
    assert br.geturl().endswith('/account/')

@then('I see a warm and welcoming message')
def impl(context):
    # Remember, context.parse_soup() parses the current response in
    # the mechanize browser.
    soup = context.parse_soup()
    msg = str(soup.findAll('h2', attrs={'class': 'welcome'})[0])
    assert "Welcome, foo!" in msg

Environment setup in "features/environment.py":

import os
# This is necessary for all installed apps to be recognized, for some reason.
os.environ['DJANGO_SETTINGS_MODULE'] = 'myproject.settings'


def before_all(context):
    # Even though DJANGO_SETTINGS_MODULE is set, this may still be
    # necessary. Or it may be simple CYA insurance.
    from django.core.management import setup_environ
    from myproject import settings
    setup_environ(settings)

    ### Take a TestRunner hostage.
    from django.test.simple import DjangoTestSuiteRunner
    # We'll use thise later to frog-march Django through the motions
    # of setting up and tearing down the test environment, including
    # test databases.
    context.runner = DjangoTestSuiteRunner()

    ## If you use South for migrations, uncomment this to monkeypatch
    ## syncdb to get migrations to run.
    # from south.management.commands import patch_for_test_db_setup
    # patch_for_test_db_setup()

    ### Set up the WSGI intercept "port".
    import wsgi_intercept
    from django.core.handlers.wsgi import WSGIHandler
    host = context.host = 'localhost'
    port = context.port = getattr(settings, 'TESTING_MECHANIZE_INTERCEPT_PORT', 17681)
    # NOTE: Nothing is actually listening on this port. wsgi_intercept
    # monkeypatches the networking internals to use a fake socket when
    # connecting to this port.
    wsgi_intercept.add_wsgi_intercept(host, port, WSGIHandler)

    def browser_url(url):
        """Create a URL for the virtual WSGI server.

        e.g context.browser_url('/'), context.browser_url(reverse('my_view'))
        """
        return urlparse.urljoin('http://%s:%d/' % (host, port), url)

    context.browser_url = browser_url

    ### BeautifulSoup is handy to have nearby. (Substitute lxml or html5lib as you see fit)
    from BeautifulSoup import BeautifulSoup
    def parse_soup():
        """Use BeautifulSoup to parse the current response and return the DOM tree.
        """
        r = context.browser.response()
        html = r.read()
        r.seek(0)
        return BeautifulSoup(html)

    context.parse_soup = parse_soup


def before_scenario(context, scenario):
    # Set up the scenario test environment
    context.runner.setup_test_environment()
    # We must set up and tear down the entire database between
    # scenarios. We can't just use db transactions, as Django's
    # TestClient does, if we're doing full-stack tests with Mechanize,
    # because Django closes the db connection after finishing the HTTP
    # response.
    context.old_db_config = context.runner.setup_databases()

    ### Set up the Mechanize browser.
    from wsgi_intercept import mechanize_intercept
    # MAGIC: All requests made by this monkeypatched browser to the magic
    # host and port will be intercepted by wsgi_intercept via a
    # fake socket and routed to Django's WSGI interface.
    browser = context.browser = mechanize_intercept.Browser()
    browser.set_handle_robots(False)


def after_scenario(context, scenario):
    # Tear down the scenario test environment.
    context.runner.teardown_databases(context.old_db_config)
    context.runner.teardown_test_environment()
    # Bob's your uncle.
Jump to Line
Something went wrong with that request. Please try again.