Cillian O'Ruanaidh / Home

Django Full Stack Testing and BDD with Lettuce and Splinter

I recently started working with Python and Django. After previously working with Rails I was looking for Python equivalents to BDD and testing tools like cucumber, rspec, and capybara or webrat.

I found lettuce and splinter. These projects are a bit newer than their ruby equivalents but you can still get the job done.

Example scenario that needs to wait for an AJAX request

Most of the problems I’ve encountered when writing full stack tests happen when you have to wait for an AJAX request. So here’s an example from a Django app with some AJAX action.

# apps/users/features/signup.feature
Scenario: User enters a username that has been taken
    Given a user exists with username "joeb"
    When I go to the "/register/" URL
    And I fill in "username" with "joeb"
    And I move focus away from the username field
    Then I should see "not available"

That scenario is for when a user is registering. As soon as the user enters their username and moves on to the next field an AJAX request goes off and checks whether that username is available or not. When it receives a response it displays “available” or “not available” beside the username field.

Setting up Lettuce and Splinter

But before you do anything you might want to setup a couple of things in your lettuce terrain.py file.

# terrain.py
from lettuce import before, after, world
from splinter.browser import Browser
from django.test.utils import setup_test_environment, teardown_test_environment
from django.core.management import call_command
from django.db import connection
from django.conf import settings

@before.harvest
def initial_setup(server):
    call_command('syncdb', interactive=False, verbosity=0)
    call_command('flush', interactive=False, verbosity=0)
    call_command('migrate', interactive=False, verbosity=0)
    call_command('loaddata', 'all', verbosity=0)
    setup_test_environment()
    world.browser = Browser('webdriver.firefox')

@after.harvest
def cleanup(server):
    connection.creation.destroy_test_db(settings.DATABASES['default']['NAME'])
    teardown_test_environment()

@before.each_scenario
def reset_data(scenario):
    # Clean up django.
    call_command('flush', interactive=False, verbosity=0)
    call_command('loaddata', 'all', verbosity=0)

@after.all
def teardown_browser(total):
    world.browser.quit()

Implementing steps

Splinter makes it easy to implement your steps and control the browser by providing simple methods like the following:

Check out the splinter docs for a full list of what you can do.

So here are the steps for the “User enters a username that has been taken” scenario.

# apps/users/features/user-steps.py
@step(u'I go to the "(.*)" URL')
def i_go_to_the_url(step, url):
    world.response = world.browser.visit(django_url(url))

@step(u'a user exists with username "(.*)"')
def a_user_exists_with_username(step, p_username):
    user = UserProfile(username=p_username, email='example@example.com')
    user.set_password('secret007')
    user.save()

@step(u'I fill in "(.*)" with "(.*)"')
def i_fill_in(step, field, value):
    world.browser.fill(field, value)

@step(u'I move focus away from the username field')
def i_move_focus_away_from_the_username_field(step):
    world.browser.fill("password", "value")
    world.browser.wait_for_xpath('//*[@id="availability" and text()="not available"]')

@step(u'I should see "(.*)"')
def i_should_see(step, text):
    assert text in world.browser.html

Running lettuce features

To run your features for the users app with a separate settings file you would use something like…

python manage.py harvest --apps=users --settings=settings_lettuce

This uses a separate settings file which is useful if you need a separate environement. For example if you want to use a sqlite3/in memory database to make your tests run quicker. Or one other thing I had to do was to reduce the global_log_level because the extra output in the terminal made it harder to read when lettuce was outputting the test results.

You can see all the code for this example at https://github.com/cillian/batucada/tree/full-stack-tests

Keep up the good work Lettuce and Splinter.