I’ve finally gotten around to writing some “integration” tests on Google App Engine. I distinguish unit tests from integration tests basically by the things that are exercised in the test. For the purposes of this post, integration tests exercise the datastore.
The GAE docs talk about how to setup a test to use the datastore. But also follow the instructions here until the docs are fully updated.
For this post, I’m using GAE 1.2.2, Eclipse 3.5, Spring 2.5.6 and the JUnit 4 implementation that comes with Eclipse 3.5. I also have a separate test project. This would likely not work out if I wanted to run the tests in the host environment.
First, we need some test setup. I have a hierarchy, which follows the Google documentation above (except their setup is for JUnit 3). At the top is LocalServiceTestCase:
public class LocalServiceTestCase extends TestCase {
@Before
public void setUpGaeEnvironment() throws Exception {
ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment());
ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")) {});
}
@After
public void tearDownGaeEnvironment() throws Exception {
// not strictly necessary to null these out but there's no harm either
ApiProxy.setDelegate(null);
ApiProxy.setEnvironmentForCurrentThread(null);
}
}
Note the @Before and @After annotations. This class useful if you just need the API environment in your test. My new tests use the datastore, however, and for that, I have extended LocalServiceTestCase with LocalDatastoreTestCase:
public class LocalDatastoreTestCase extends LocalServiceTestCase {
@Before
public void setUpDatastore() throws Exception {
ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY, Boolean.TRUE.toString());
}
@After
public void tearDownDatastore() throws Exception {
ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
LocalDatastoreService datastoreService = (LocalDatastoreService) proxy.getService("datastore_v3");
datastoreService.clearProfiles();
}
}
This relies on the implementation of TestEnvironment, which I made out of the Google documentation with updates from the Google group link.
public class TestEnvironment implements ApiProxy.Environment {
@Override
public String getAppId() {
return "Unit Tests";
}
@Override
public String getVersionId() {
return "1.0";
}
@Override
public String getRequestNamespace() {
return "";
}
@Override
public String getAuthDomain() {
throw new UnsupportedOperationException();
}
@Override
public boolean isLoggedIn() {
throw new UnsupportedOperationException();
}
@Override
public String getEmail() {
throw new UnsupportedOperationException();
}
@Override
public boolean isAdmin() {
throw new UnsupportedOperationException();
}
@Override
public Map<String, Object> getAttributes() {
return new HashMap<String, Object>();
}
}
With all of that available for extension, the test can focus on testing the application code:
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class })
@ContextConfiguration(locations = { "classpath:app-context.xml", "classpath:integration-context.xml",
"classpath:jdo-context.xml", "classpath:security-context.xml" })
public class UserDaoTest extends LocalDatastoreTestCase {
@Autowired
private UserDao userDao;
@Before
public void insertTestUserNamedFred() {
...
}
@Test
public void findByName() {
User user = userDao.findByName("fred");
assertNotNull(user);
}
}
Note the use of the @RunWith annotation that sets up the test to use an alternative test runner. In this case, we’ll use Spring’s test runner, which allows the addition of the subsequent annotations.
Next, Spring’s @TestExecutionListeners comes into play in order to get dependency injection to work using DependencyInjectionTestExecutionListener.
Finally, we tell the test where to find the context configurations. The @ContextConfiguration annotation references configurations in the classpath. I currently have the directory containing these configurations added to the classpath of my test project.
Run that using JUnit 4 and the autowiring should take effect, wiring up everything you need to test your class. Too bad autowiring doesn’t work on the server. So if you need to run your tests on the server, user setter injection.
That should be all you need. Hope this helps.
Some other things that could be done include a base class with all of those annotations and a context configuration that didn’t require each configuration file to be listed (I don’t even know if that’s possible, however).