My favorites | English | Sign in

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

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;

import java.util.HashMap;
import java.util.Map;

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

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

  public String getEmail() {
    throw new UnsupportedOperationException();
  }

  public boolean isLoggedIn() {
    throw new UnsupportedOperationException();
  }

  public boolean isAdmin() {
    throw new UnsupportedOperationException();
  }

  public String getAuthDomain() {
    throw new UnsupportedOperationException();
  }

  public String getRequestNamespace() {
    return "";
  }

  public Map<String, Object> getAttributes() {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("com.google.appengine.server_url_key", "http://localhost:8080");
    return map;
  }
}

// ...

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;

import junit.framework.TestCase;

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;
import com.google.apphosting.api.ApiProxy;

public class BugNotificationTest extends LocalServiceTestCase {

    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;
import com.google.apphosting.api.ApiProxy;

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(LocalDatastoreService.PACKAGE);
        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());
    }
}

Writing Task Queue Tests

When you're testing code that uses the Task Queue API you'll most likely want to assert that a task was scheduled with the expected properties. Here's a first attempt at a test that does just that:

import com.google.appengine.api.labs.taskqueue.dev.LocalTaskQueue;
import com.google.appengine.api.labs.taskqueue.dev.QueueStateInfo;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskOptions;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;

public class TaskSubmissionTest extends LocalServiceTestCase {

    public void testTaskGetsScheduled() {
        QueueFactory.getDefaultQueue().add(TaskOptions.Builder.taskName("task29"));
        ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
	LocalTaskQueue ltq = (LocalTaskQueue) proxy.getService(LocalTaskQueue.PACKAGE);
	String defaultQueueName = QueueFactory.getDefaultQueue().getQueueName();
        QueueStateInfo qsi = ltq.getQueueStateInfo().get(defaultQueueName);
        assertEquals(1, qsi.getTaskInfo().size());
        assertEquals("task29", qsi.getTaskInfo().get(0).getTaskName());
    }
}

This test almost does what you want. Almost. There are two problems, do you see them? The first problem is that we're not cleaning up after ourselves, so depending on when the task runs it may live in-memory across tests. That's undesirable because maintaining state across tests is a really easy way to end up with non-deterministic test suites. The second problem is that we've got a race condition. Unless you build your TaskOptions instance with a delay or a specific execution time, tasks run as soon as possible. How soon is that? It depends, and that's the problem. If the task runs before our assertion on the number of tasks in the default queue our test will fail. If the task runs after our test will succeed. This is not a good situation to be in. So, in addition to clearing out all tasks in between tests we also want to prevent tasks from automatically running. Let's fix this with an extension to LocalServiceTestCase:

import com.google.appengine.api.labs.taskqueue.dev.LocalTaskQueue;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;

public class LocalTaskQueueTestCase extends LocalServiceTestCase {

    @Override
    public void setUp() throws Exception {
        super.setUp();
        ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
	// prevent tasks from automatically executing
        proxy.setProperty(LocalTaskQueue.DISABLE_AUTO_TASK_EXEC_PROP, Boolean.TRUE.toString());
    }

    @Override
    public void tearDown() throws Exception {
        ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
	LocalTaskQueue ltq = (LocalTaskQueue) proxy.getService(LocalTaskQueue.PACKAGE);
	// clear out all tasks in all queues
        for (String queueName : ltq.getQueueStateInfo().keySet()) {
            ltq.flushQueue(queueName);
        }
        super.tearDown();
    }
}

Now we can modify our original task queue test to extend this base class and not have to worry about race conditions or task state leaking across tests:

// ... imports
public class TaskSubmissionTest extends LocalTaskQueueTestCase {
// ...
}