English
Skip to page content Skip to main navigation

e.g. "templates" or "datastore"

# Unit Testing With Local Service Implementations

Writing unit tests that make use of the local service implementations that are bundled with the SDK is a natural and healthy thing to do. This chapter describes how to accomplish this task with JUnit 3. We'll go through the individual steps one by one and then tie them all together in a base class that your tests can extend.

- Establishing The Execution Environment
- Creating A Base TestCase
- Writing Datastore Tests

## Establishing The Execution Environment

The first thing you'll need to do is make sure you have `appengine-api-stubs.jar` and `appengine-local-runtime.jar` on the classpath for your unit test (these jars ship as part of the SDK). Next, we need to create an instance of `ApiProxy.Environment` and register it with `ApiProxy`. When your application is running locally the container creates this on your behalf using the contents of your `appengine-web.xml` config file, but in this case JUnit is our container, and JUnit doesn't know anything at all about App Engine or `appengine-web.xml`. So, it's up to the test to make sure an `ApiProxy.Environment` is properly constructed and registered:

    import com.google.apphosting.api.ApiProxy;

    class TestEnvironment implements ApiProxy.Environment {
        public String getAppId() {
            return "Unit Tests";
        }

        public String getVersionId() {
            return "1.0";
        }

        public void setDefaultNamespace(String s) { }

        public String getRequestNamespace() {
            return "gmail.com";
        }

        public String getDefaultNamespace() {
            return "";
        }

        public String getAuthDomain() {
            return "gmail.com";
        }

        public boolean isLoggedIn() {
            return false;
        }

        public String getEmail() {
            return "";
        }

        public boolean isAdmin() {
            return false;
        }
    }

    // ...

    ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment());

Once we've established an environment for the local service implementations to execute within, we need to set up the local service implementations themselves. The App Engine services that your application uses ultimately invoke `ApiProxy.makeSyncCall()`, which in turn delegates to an `ApiProxy` instance that, depending on whether you're running locally or in production, communicates with a local or a remote server-side implementation of the service. When you run your app locally, the container installs an implementation of `ApiProxy` called `ApiProxyLocalImpl`. In this case, however, our container is JUnit, and JUnit doesn't know that this needs to be set up. So, it's up to you to do it:

    import java.io.File;
    import com.google.appengine.tools.development.ApiProxyLocalImpl;
    import com.google.apphosting.api.ApiProxy;

    ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")){});

And that's it!

## Creating A Base TestCase

We want to make it easy to write tests that make use of local service implementations, so let's wrap this up in a base TestCase that your tests can easily extend:

    import com.google.appengine.tools.development.ApiProxyLocalImpl;
    import com.google.apphosting.api.ApiProxy;

    public class LocalServiceTestCase extends TestCase {

        @Override
        public void setUp() throws Exception {
            super.setUp();
            ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment());
            ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")){});
        }

        @Override
        public void tearDown() throws Exception {
            // not strictly necessary to null these out but there's no harm either
            ApiProxy.setDelegate(null);
            ApiProxy.setEnvironmentForCurrentThread(null);
            super.tearDown();
        }
    }

Now that we have a base TestCase it's simple to write tests that depend on local service implementations. Here's an example that tests that emails are properly sent when a bug gets created:

    import com.google.appengine.api.mail.dev.LocalMailService;
    import com.google.appengine.tools.development.ApiProxyLocalImpl;

    public class BugNotificationTest extends TestCase {

        public void testEmailGetsSent() {

            ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
            LocalMailService mailService = (LocalMailService) proxy.getService("mail");
            mailService.clearSentMessages();

            Bug b = new Bug();
            b.setSeverity(Severity.LOW);
            b.setText("NullPointerException when trying to update phone number.");
            b.setOwner("max");
            new BugDAO().createBug(b);

            assertEquals(1, mailService.getSentMessages().size());
            // ... tests the content and recipient of the email
        }
    }

## Writing Datastore Tests

By default, the local implementation of the datastore service flushes its contents to disk at regular intervals. When you're running your application locally this is a nice feature because it maintains your state after you shut down the server. When you're running tests, however, it's much easier to write deterministic tests if every test starts with a clean datastore, and having state flushed to disk and then read back in can get in the way. Let's extend LocalServiceTestCase to demonstrate how to achieve this:

    import com.google.appengine.api.datastore.dev.LocalDatastoreService;
    import com.google.appengine.tools.development.ApiProxyLocalImpl;

    public class LocalDatastoreTestCase extends LocalServiceTestCase {

        @Override
        public void setUp() throws Exception {
            super.setUp();
            ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
            proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY, Boolean.TRUE.toString());
        }

        @Override
        public void tearDown() throws Exception {
            ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
            LocalDatastoreService datastoreService = (LocalDatastoreService) proxy.getService("datastore_v3");
            datastoreService.clearProfiles();
            super.tearDown();
        }

    }

Now any test belonging to a TestCase that extends this class can be sure that it is starting with a clean datastore:

    import com.google.appengine.api.datastore.DatastoreServiceFactory;
    import com.google.appengine.api.datastore.DatastoreService;
    import com.google.appengine.api.datastore.Query;

    public class ContactInfoDAOTestCase extends LocalDatastoreTestCase {

        public void testBatchInsert() {
            ContactInfoDAO dao = new ContactInfoDAO();
            ContactInfo info1 = new ContactInfo("John", "Doe", "650-555-5555");
            ContactInfo info2 = new ContactInfo("Jane", "Doe", "415-555-5555");
            dao.addContacts(info1, info2);

            Query query = new Query(ContactInfo.class.getSimpleName());
            assertEquals(2, DatastoreServiceFactory.getDatastoreService().prepare(query).countEntities());
        }

        public void testDelete() {
            ContactInfoDAO dao = new ContactInfoDAO();
            ContactInfo info1 = new ContactInfo("John", "Doe", "650-555-5555");
            dao.addContact(info1);

            dao.deleteContact(info1);
            Query query = new Query(ContactInfo.class.getSimpleName());
            assertEquals(0, DatastoreServiceFactory.getDatastoreService().prepare(query).countEntities());
        }
    }