objectuser.blog(brain)

objectuser.getBlog().contains(anything)

  • a place for thoughts, explanations, etc.

    this is just a place to write things out so I can make sure i understand them, keep track of them, and to use them again as links, etc.
  • Subscribe

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).

About these ads

11 Responses to “Google App Engine Testing with Spring”

  1. 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?

  2. 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.

  3. 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.

  4. niraj said

    thanks a lot.

    This post is very useful

  5. […] 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 […]

  6. Chandana said

    Hi,

    I want to know Which JAR file include “ApiProxyLocalImpl” class file

    Thank You.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: