Local Unit Testing Google App Engine with Agar

Agar is an extraction from the many Google App Engine applications that have been built at Best Buy. Agar is part of the Substrate project but it can be used separately.

One of the nice features of Agar is its support for local unit testing of App Engine projects. The App Engine SDK now comes with google.appengine.ext.testbed to help with this. Testbed came from the gaetestbed project (which is now unmaintained) and contains the same main core feature: the state of the application is reset between tests.

However, gaetestbed had many useful unit test assertions which were not brought along into the App Engine SDK. The agar.test module closes this gap with a set of assertions which are mostly-compatable with gaetestbed. It also provides easy-to-use base classes to avoid boilerplate setup code in tests.

Finally, agar.test provides a mocked version of the urlfetch API proxy stub, making it easy to test code that hits external web services without making real (slow) network connections.

To get started, install Agar, either by downloading the source code or as part of Substrate. Substrate also includes a management script that has a test runner, should you need one.

In your unit tests, you can now inherit from agar.test.BaseTest. This base class takes care of setting up the API proxy stubs, as shown in this article. Unlike gaetestbed, agar.test is not split into multiple base classes for each API service: they are all in BaseTest.

BaseTest also provides assertions and helper methods, mostly based on gaetestbed’s API, but with some new ones like log_in_user which will make users.get_current_user return a users.User with the email address of your choice.

Like gaetestbed, agar.test provides a wrapper around WebTest to make it easier to use and adds assertions for common HTTP responses. Here is how you use it:

from agar.test import BaseTest, WebTest
import my_app

class TestMyApp(BaseTest, WebTest):

    APPLICATION = my_app.application

    def test_get_home_page(self):
        response = self.get("/")
        self.assertOK(response)
        
def test_post_as_user(self):
    log_in_user('test@example.com')
    response = self.post("/articles", {'title': 'New Article,
                                       'body': 'Hello world'})
    self.assertRedirects(response, to="/")

The agar.test.WebTest class adds some additional HTTP assertions but is otherwise similar to gaetestbed’s API.

Finally, agar.test adds another test base class for simulating HTTP connections. If you’ve used libraries like FakeWeb for Ruby you know how useful this can be!

Because you may not always want to simulate HTTP connections, this is separated out into its own base class: MockUrlfetchTest. To use it, extend from agar.test.MockUrlfetchTest, and in your setUp or test_* methods, register the URLs that will be requested and the response you want to simulate with set_response:

class MyHTTPTest(MockUrlfetchTest):
   def setUp(self):
       super(MyHTTPTest, self).setUp()

       self.set_response("http://www.google.com/blah", 
                         content="foobar", status_code=404)

   def test_get_google(self):
       result = urlfetch.fetch("http://www.google.com/blah")

       self.assertEqual(404, result.status_code)
       self.assertEqual("foobar", result.content)

Any requests for URLs that have not been registered will raise an exception:

def test_this_will_fail(self):
    result = urlfetch.fetch("http://www.example.com/")

You can also register an instance of urlfetch.DownloadError as the content, and it will be raised when that URL is requested. This is useful for testing urlfetch timeouts.

# Will cause a DownloadError to be raised when the URL is requested:
from google.appengine.api import urlfetch
self.set_response("http://example.com/boom", 
                  response=urlfetch.DownloadError("Something Failed"))

I’ve found the gaetestbed assertions to be invaluable in my Google App Engine development, so I was sad to see that their development was not continued by Google. However, now agar.test is a welcome substitute, and I hope it helps you as much as it has me. If it doesn’t do something you need, patches are welcome!