AppEngine Unit Testing Made Easy with JUnit Rules

If you‘ve built an application on Google App Engine, chances are you‘re taking advantage of one or more of the platform‘s handy services, such as:

  • Datastore for persistent storage
  • Memcache for caching
  • Task Queues for background processing
  • Mail, URL Fetch, and User services

While App Engine makes it easy to plug these services into your app, things can get tricky when it comes time to write unit tests. To test code that interacts with App Engine services, you need to set up a local testing environment that mimics the production environment.

Fortunately, Google provides some great documentation on how to configure your tests to use local service stubs, complete with sample code. Here‘s an example of setting up a test to interact with a local Datastore:

LocalServiceTestHelper helper = new LocalServiceTestHelper(
        new LocalDatastoreServiceTestConfig());
helper.setUp();

DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

Entity testEntity = new Entity("TestEntity");
testEntity.setProperty("name", "Joe");
ds.put(testEntity);

assertEquals(1, ds.prepare(new Query("TestEntity")).countEntities());

helper.tearDown();

This works great for simple cases, but most real-world App Engine apps rely on multiple services. You might be using Memcache to cache entities loaded from Datastore, enqueueing Task Queue tasks after key operations, and validating the current user with User Service. Configuring the local test environment for all these services quickly bloats your test code:

LocalServiceTestHelper helper = new LocalServiceTestHelper(
        new LocalDatastoreServiceTestConfig(),
        new LocalMemcacheServiceTestConfig(),
        new LocalTaskQueueTestConfig(),
        new LocalUserServiceTestConfig())
            .setEnvIsLoggedIn(true)
            .setEnvAuthDomain("example.com")
            .setEnvEmail("[email protected]");

helper.setUp();

// Your actual test code here

helper.tearDown();

And that‘s not all. What if you need to change specific config for certain tests, like using High Replication Datastore, changing the queue XML path, or testing a different logged in user? Your test setup code will grow even more complex.

Even worse, you‘ll end up duplicating this complex setup code in every single test class, making changes tedious.

You might think,"I know, I‘ll create a base test class with all the setup code, and have my test classes extend it!" Not so fast. The setup code creates instances of the service test stubs, which can be expensive. Best practice is to only incur that cost for the specific services each test needs. Setting everything up in a base class will cause unnecessary work and slow down your test suite.

Simplify Service Setup with JUnit Rules

Luckily, there‘s a better way: JUnit Rules!

According to the JUnit docs, "Rules allow very flexible addition or redefinition of the behavior of each test method in a test class." You can use the built-in rules, extend a rule class, or write your own.

The key feature of rules is they let you intercept test execution to run code before and after the test. This is perfect for setting up and tearing down external dependencies like App Engine services.

Here‘s an example of a custom AppEngineRule that configures the local test services:

public class AppEngineRule extends ExternalResource {

    private LocalServiceTestHelper helper;

    private AppEngineRule(LocalServiceTestHelper helper) {
        this.helper = helper;
    }

    @Override
    protected void before() throws Throwable {
        super.before();
        helper.setUp();
    }

    @Override
    protected void after() {
        super.after();
        helper.tearDown();
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {

        private final LocalServiceTestHelper helper = 
            new LocalServiceTestHelper();

        public Builder withDatastore() {
            helper.setEnvIsLoggedIn(true); 
            return this;
        }

        public Builder withMemcache() {
            helper.setEnvIsLoggedIn(true);
            return this;
        }

        public Builder withUserService() {
            helper.setEnvIsLoggedIn(true)
                .setEnvAuthDomain("example.com")  
                .setEnvEmail("[email protected]");
            return this;
        }

        public Builder withTaskQueue() {
            helper.setEnvIsLoggedIn(true);
            return this;
        }

        public AppEngineRule build() {
            return new AppEngineRule(helper);
        }        
    }
}

This rule class uses the builder pattern, allowing you to fluently configure the specific services needed in each test. Here‘s how you would use it in a test class:

public class MyTest {

    @Rule
    public final AppEngineRule appEngine = AppEngineRule.builder()
        .withDatastore()
        .withUserService()
        .build();

    @Test
    public void testSomething() {
        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
        UserService userService = UserServiceFactory.getUserService();

        // Test code here
    }
}

Much cleaner! The rule handles setting up the Datastore and User services before each test and tearing them down afterwards. No more duplicate, bloated setup code.

Using a Library for Common App Engine Rules

Writing your own rule classes is a great way to tailor your test setup. But if you‘re looking to save even more time, check out the gae-test-util-java library. It provides pre-built JUnit rules for all the App Engine services.

To use it, first add the dependency to your build tool of choice. For example, in Gradle:

testCompile ‘com.github.ramesh-dev:gae-test-util-java:0.3‘

Then, simply declare the rules you need in your test class:

public class MyTest {

    @Rule 
    public final LocalDatastoreRule datastore = 
        new LocalDatastoreRule();

    @Rule
    public final LocalMemcacheRule memcache =
        new LocalMemcacheRule();

    @Rule
    public final LocalTaskQueueRule taskQueues =
        new LocalTaskQueueRule();

    @Rule
    public final LocalUserServiceRule userService =
        new LocalUserServiceRule()
            .loginUser("[email protected]");

    // Test code here
}

The library provides tons of flexible options for configuring the services to fit your needs.

Conclusion

Unit testing App Engine services doesn‘t have to be a headache. With the power of JUnit Rules, you can keep your test setup code concise, maintainable, and tailored to your app‘s needs.

Whether you write your own custom rule classes or leverage an open-source library, JUnit Rules are an essential tool for any App Engine developer. They‘ll help make your tests cleaner, faster, and more reliable.

The approach outlined here transformed my own experience writing unit tests for App Engine from tedious to enjoyable. I encourage you to give it a try in your own projects. You‘ll never go back to messy setup code again!

Happy testing!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *