Google App Engine Testing with Spring
Posted by objectuser on August 2, 2009
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).
Alexei Vidmich said
Great article. Very useful.
I was wondering if you run such tests with maven?
If yes, than how did you make it enhance the entity classes before running tests?
objectuser said
Glad it was helpful.
I don’t use maven, no. I just run them within Eclipse. I’m not really a maven guy though everyone tells me I should be.
Vikas Hazrati said
and how do you do the enhancement step within eclipse? Is it done manually?
Shane Witbeck said
On line 5 of your LocalServiceTestCase class you use ApiProxyLocalImpl. Where is this coming from?
Thanks.
objectuser said
It’s in appengine-local-runtime.jar. Check out the “setup a test” link; you need that jar and one other.
musky58 said
Great job getting all these articles set up for GAE! Tried setting this up following the example above. I am getting two errors with the libs:
junit-4.7
spring 3.0.0RC1
GAE 1.2.6
1) is just a NULL pointer on the DAO (Are you using a @Component at the interface or Impl or @Resource?)
2) When I run another unit test stating it couldn’t find a test but I have tried both @Test and testMethod() both produce the same behavior. prior to the change both worked
any pointers?
junit.framework.AssertionFailedError: No tests found in com.knests.webosappcat.test.dao.jdo.CategoryDAOTest
at junit.framework.Assert.fail(Assert.java:47)
at junit.framework.TestSuite$1.runTest(TestSuite.java:97)
at junit.framework.TestCase.runBare(TestCase.java:134)
at junit.framework.TestResult$1.protect(TestResult.java:110)
at junit.framework.TestResult.runProtected(TestResult.java:128)
at junit.framework.TestResult.run(TestResult.java:113)
at junit.framework.TestCase.run(TestCase.java:124)
at junit.framework.TestSuite.runTest(TestSuite.java:232)
at junit.framework.TestSuite.run(TestSuite.java:227)
at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
objectuser said
Interesting. I’m not sure what’s going on with your setup. Are you choosing the right JUnit test runner?
I’m not sure what you mean about using @Component or @Resource. I’m using Spring 2.5, so it could be that my setup just won’t work in that environment.
niraj said
thanks a lot.
This post is very useful
Google App Engine Testing with Spring Updated for SDK 1.3.1 « objectuser.blog(brain) said
[...] JDO in Google App EngineSpring + JPA in Google App EngineQueries in GAE: Many to Many RelationshipsGoogle App Engine Testing with SpringMore on Spring Security in Google App EngineGoogle App EngineModeling in GAE: Element and [...]
Chandana said
Hi,
I want to know Which JAR file include “ApiProxyLocalImpl” class file
Thank You.
objectuser said
See the commet above.