user1768830
user1768830

Reputation:

Testing Google App Engine ThreadManager outside of GAE

I've written a JUnit (4.10) unit test that makes the following call to com.google.appengine.api.ThreadManager:

ThreadManager.currentRequestThreadFactory();

When this test runs, I get a NullPointerException being thrown from within this currentRequestThreadFactory method:

Caused by: java.lang.NullPointerException
    at com.google.appengine.api.ThreadManager.currentRequestThreadFactory(ThreadManager.java:39)
    at com.myapp.server.plumbing.di.BaseModule.providesThreadFactory(BaseModule.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

When I pull down the source for ThreadManager, and look at Line 39 (which is the source of the NPE), I see:

public static ThreadFactory currentRequestThreadFactory() {
        return (ThreadFactory) ApiProxy.getCurrentEnvironment().getAttributes()
            .get(REQUEST_THREAD_FACTORY_ATTR);
}

So it seems that the ApiProxy.getCurrentEnvironment() is null, and when it's getAttribute() method is called, the NPE is thrown. I've confirmed this by adding some new print statements higher up in my unit test code:

if(ApiProxy.getCurrentEnvironment() == null)
    System.out.println("Environment is null.");

I'm vaguely aware that GAE offers "test versions" for all its services, but haven't been able to find (specifically) how to use them and set them up. So I ask: does GAE offer such test versions? If so, how do I add an ApiProxy test version here? And if not, then what are my options? I don't think I can mock either method (ThreadManager#currentRequestThreadFactory or ApiProxy#getCurrentEnvironment) because they're both statics. Thanks in advance.

Edit: I see that there is an appengine-testing.jar that ships with the SDK. Inside this JAR is an ApiProxyLocal.class that I believe is a version of ApiProxy that could be used during JUnit testing, that would work without throwing the NPE. If that's the case (which I'm not even sure of), then the question is: how do I inject it into my ThreadManager for this test?

Upvotes: 7

Views: 1504

Answers (4)

user2923097
user2923097

Reputation: 81

You will get the correct Threads from the stubs, if you set up your LocalServiceTestHelper, along the following lines.

private static final LocalServiceTestHelper helper = new LocalServiceTestHelper( new    LocalDatastoreServiceTestConfig());

@BeforeClass
public static void initialSetup() {
    helper.setUp();
}

@AfterClass
public static void finalTearDown() {
    helper.tearDown();
}

Upvotes: 4

Nichole
Nichole

Reputation: 1

To add to the last answer, if you are starting a thread within your unit test case, that java thread needs the ApiProxy environment set too.

in your class LocalServiceTestCase extends TestCase, the setup method might look something like this:

super.setUp();

helper1.setUp();

setEnvironment();

where:

public static void setEnvironment() {

    if (ApiProxy.getCurrentEnvironment() == null) {  

        ApiProxyLocal apl = LocalServiceTestHelper.getApiProxyLocal();

        ApiProxy.setEnvironmentForCurrentThread(new TEnvironment());

        ApiProxy.setDelegate(apl);

    }
}

The TEnvironment is given in the above urls.

You might want to make a static unsetEnvironment() too.

In your unit test case, if you've started a new java thread, you can just use the static method at the beginning of your run method:

public void run() {

    LocalServiceTestCase.setEnvironment();

Upvotes: 0

Nichole
Nichole

Reputation: 1

If you're implementing ApiProxy.Environment as suggested in http://code.google.com/appengine/docs/java/howto/unittesting.html

then the getAttributes() method is where you are not finding an entry for REQUEST_THREAD_FACTORY_ATTR in your map.

You can add that:

attributes.put(REQUEST_THREAD_FACTORY_ATTR, new RequestThreadFactory());

For other helpful additions, see: http://googleappengine.googlecode.com/svn-history/trunk/java/src/main/com/google/appengine/tools/development/LocalEnvironment.java

Upvotes: 0

NamshubWriter
NamshubWriter

Reputation: 24286

I suggest avoiding calling ThreadManager.currentRequestThreadFactory() directly from your code. Instead, inject the ThreadFactory into the class that needs to create threads.

One easy way to do this is with Guice:

public static class MyService {
  private final ThreadFactory threadFactory;

  @Inject
  MyService(ThreadFactory threadFactory) {
    this.threadFactory = threadFactory;
  }

  ...
}

In your tests for MyService, you could either pass a fake ThreadFactoryto the MyService constructor or inject Executors.defaultThreadFactory().

In your production code, you would create a binding:

bind(ThreadFactory.class)
    .toInstance(ThreadManager.currentRequestThreadFactory());

Of course, if you're not ready to jump into dependency injection, you can create your own accessors to get and set the ThreadFactory

Upvotes: 1

Related Questions