Saturday, September 27, 2008

Mocking with Django and Google AppEngine

Since working with RSpec over the past 6 months - a Behaviour-Driven Development framework for ruby - I've been wondering if there's anything comparable in Python (my preferred tool for development!). One of the things I love about RSpec is the ease with which Mock objects can be used to keep tests focused.

While there are a number of mock libraries around for Python most don't result in particularly readable test code. But I was pleasantly suprised to discover Michael Foord's Mocking and Testing utilities.

A simple example: I've got a Django application hosted on Google AppEngine and say I want to write a simple test to verify that my view does require the user to be logged in with their Google account and if not, redirects appropriately - but I don't want to have to manually log a user in, or even use the Google api as part of my test. Here's a snippet showing how easy this is with Mock:

from mock import Mock
from google.appengine.api import users


class MySpecialView(TestCase):

def setUp():
""" Create the required mocks for the view tests """
# Mock the get_current_user method of the google users api
users.get_current_user = Mock()

# Create a mock user that we'll pretend is logged in
mock_user = Mock()

# Just for readability, save the special app-engine login url as
# an instance variable
self.url = reverse('my-special-view-name')
self.login_url = "http://testserver/_ah/login?continue=http%%3A//testserver%s" % self.url

def test_logged_in_user_can_access_page(self):
"""A logged in user should not be redirected to the login page"""
# Set the return value for the mocked
# get_current_user method:
users.get_current_user.return_value = mock_user

response = do_request()

# Make sure the mock method was called
self.assertTrue(users.get_current_user.called)

# And the redirect did not take place, but the
# normal template was rendered...
self.assertTemplateUsed(response, 'myapp/overview.html')

def test_anonymous_user_is_redirected_to_login(self):
""" An anonymous user should be redirected to the login page"""
# Set the google api's get_current_user method to return None
users.get_current_user.return_value = None

response = self.do_request()

# Make sure the mock method was called
self.assertTrue(users.get_current_user.called)

# And that the redirect took place... note we can't use
# the normal assertRedirects due to the app-engine specific
# login url.
self.assertEquals(response.status_code, 302)
self.assertEquals(
response['location'],
"http://testserver/_ah/login?continue=http%%3A//testserver%s" % self.url
)


Easy! Thanks Michael. The Mock object has lots of other goodies of course (such as auto-setting all the mock-methods from the real object, testing for call parameters etc.).