1 // Copyright 2009 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.tools
.development
.testing
;
4 import com
.google
.appengine
.api
.NamespaceManager
;
5 import com
.google
.appengine
.tools
.development
.ApiProxyLocal
;
6 import com
.google
.appengine
.tools
.development
.ApiProxyLocalFactory
;
7 import com
.google
.appengine
.tools
.development
.Clock
;
8 import com
.google
.appengine
.tools
.development
.LocalEnvironment
;
9 import com
.google
.appengine
.tools
.development
.LocalRpcService
;
10 import com
.google
.appengine
.tools
.development
.LocalServerEnvironment
;
11 import com
.google
.apphosting
.api
.ApiProxy
;
12 import com
.google
.apphosting
.api
.ApiProxy
.Environment
;
14 import java
.util
.Arrays
;
15 import java
.util
.List
;
17 import java
.util
.TimeZone
;
18 import java
.util
.concurrent
.ConcurrentHashMap
;
19 import java
.util
.concurrent
.ConcurrentMap
;
20 import java
.util
.logging
.Level
;
21 import java
.util
.logging
.Logger
;
24 * Helper class for testing against local app engine services.
25 * Construct the helper with one {@link LocalServiceTestConfig} instance for
26 * each service that you wish to access as part of your test. Then call
27 * {@link #setUp()} before each test executes and {@link #tearDown()} after
28 * each test executes. No specific test-harness is assumed, but here's a
29 * JUnit 3 example that uses task queues and the datastore.
33 * public void MyTest extends TestCase {
35 * private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
36 * new LocalTaskQueueTestConfig(), new LocalDatastoreServiceTestConfig());
39 * public void setUp() {
45 * public void tearDown() {
54 public class LocalServiceTestHelper
{
55 public interface RequestMillisTimer
{
57 * @return The amount of time that the {@link Environment} should say is left in the request.
58 * Expressed in milliseconds. If infinite time is allowed, then reply with
59 * {@link Long#MAX_VALUE}.
61 long getRemainingMillis();
64 * The Timer instance used by local services if no override is provided via
65 * {@link LocalServiceTestHelper#setRemainingMillisTimer(RequestMillisTimer)}.
67 RequestMillisTimer DEFAULT
= new RequestMillisTimer() {
69 public long getRemainingMillis() {
70 return Long
.MAX_VALUE
;
75 private static final String APPS_NAMESPACE_KEY
=
76 NamespaceManager
.class.getName() + ".appsNamespace";
78 private static ApiProxyLocal apiProxyLocal
;
80 static final String DEFAULT_APP_ID
= "test";
81 static final String DEFAULT_VERSION_ID
= "1.0";
83 private final Logger logger
= Logger
.getLogger(getClass().getName());
84 private final List
<LocalServiceTestConfig
> configs
;
85 private String envAppId
= DEFAULT_APP_ID
;
86 private String envVersionId
= DEFAULT_VERSION_ID
;
87 private String envEmail
;
88 private boolean envIsLoggedIn
;
89 private boolean envIsAdmin
;
90 private String envAuthDomain
;
91 private RequestMillisTimer timer
= RequestMillisTimer
.DEFAULT
;
92 private ConcurrentMap
<String
, Object
> envAttributes
= new ConcurrentHashMap
<String
, Object
>();
94 private boolean enforceApiDeadlines
= false;
95 private boolean simulateProdLatencies
= false;
97 private TimeZone timeZone
= TimeZone
.getTimeZone("UTC");
99 private TimeZone originalDefaultTimeZone
;
102 * Constructs a LocalServiceTestHelper with the provided configurations.
104 * @param configs for the local services that need to be set up and torn down.
106 public LocalServiceTestHelper(LocalServiceTestConfig
... configs
) {
107 this.configs
= Arrays
.asList(configs
);
111 * The value to be returned by
112 * {@code ApiProxy.getCurrentEnvironment().getAppId()}
115 * @return {@code this} (for chaining)
117 public LocalServiceTestHelper
setEnvAppId(String envAppId
) {
118 this.envAppId
= envAppId
;
123 * The value to be returned by
124 * {@code ApiProxy.getCurrentEnvironment().getVersionId()}
126 * @param envVersionId
127 * @return {@code this} (for chaining)
129 public LocalServiceTestHelper
setEnvVersionId(String envVersionId
) {
130 this.envVersionId
= envVersionId
;
135 * The value to be returned by
136 * {@code ApiProxy.getCurrentEnvironment().getEmail()}
139 * @return {@code this} (for chaining)
141 public LocalServiceTestHelper
setEnvEmail(String envEmail
) {
142 this.envEmail
= envEmail
;
147 * The value to be returned by
148 * {@code ApiProxy.getCurrentEnvironment().isLoggedIn()}
150 * @param envIsLoggedIn
151 * @return {@code this} (for chaining)
153 public LocalServiceTestHelper
setEnvIsLoggedIn(boolean envIsLoggedIn
) {
154 this.envIsLoggedIn
= envIsLoggedIn
;
159 * The value to be returned by
160 * {@code ApiProxy.getCurrentEnvironment().isAdmin()}
163 * @return {@code this} (for chaining)
165 public LocalServiceTestHelper
setEnvIsAdmin(boolean envIsAdmin
) {
166 this.envIsAdmin
= envIsAdmin
;
171 * The value to be returned by
172 * {@code ApiProxy.getCurrentEnvironment().getAuthDomain()}
174 * @param envAuthDomain
175 * @return {@code this} (for chaining)
177 public LocalServiceTestHelper
setEnvAuthDomain(String envAuthDomain
) {
178 this.envAuthDomain
= envAuthDomain
;
183 * Sets the object that will return the value to be returned by
184 * {@code ApiProxy.getCurrentEnvironment().getRemainingMillis()}
186 * @param timer The timer that returns the amount of time left.
187 * @return {@code this} (for chaining)
189 public LocalServiceTestHelper
setRemainingMillisTimer(RequestMillisTimer timer
) {
195 * The value to be returned by
196 * {@code ApiProxy.getCurrentEnvironment().getRequestNamespace()}
198 * @param envRequestNamespace
199 * @return {@code this} (for chaining)
201 public LocalServiceTestHelper
setEnvRequestNamespace(String envRequestNamespace
) {
202 envAttributes
.put(APPS_NAMESPACE_KEY
, envRequestNamespace
);
207 * The value to be returned by
208 * {@code ApiProxy.getCurrentEnvironment().getAttributes()}
210 * @param envAttributes
211 * @return {@code this} (for chaining)
213 public LocalServiceTestHelper
setEnvAttributes(Map
<String
, Object
> envAttributes
) {
214 this.envAttributes
= new ConcurrentHashMap
<String
, Object
>(envAttributes
);
219 * Sets the clock with which all local services will be initialized. Note
220 * that once a local service is initialized its clock cannot be altered.
223 * @return {@code this} (for chaining)
225 public LocalServiceTestHelper
setClock(Clock clock
) {
231 * Determines whether or not API calls should be subject to the same
232 * deadlines as in production. The default is {@code false}.
234 * @return {@code this} (for chaining)
236 public LocalServiceTestHelper
setEnforceApiDeadlines(boolean val
) {
237 this.enforceApiDeadlines
= val
;
242 * Determines whether or not local services should simulate production
243 * latencies. The default is {@code false}.
245 * @return {@code this} (for chaining)
247 public LocalServiceTestHelper
setSimulateProdLatencies(boolean val
) {
248 this.simulateProdLatencies
= val
;
253 * Sets the time zone in which tests will execute. If not set we use the
254 * same timezone that we use in production and the dev appserver: UTC. Note
255 * that if your code has permission to modify the <code>user.timezone</code>
256 * system property, this will change the default timezone for the JVM.
257 * However, if your code does not have this permission, the timezone will only
258 * be altered for the current thread.
260 * @param timeZone the time zone
261 * @return {@code this} (for chaining)
263 public LocalServiceTestHelper
setTimeZone(TimeZone timeZone
) {
264 this.timeZone
= timeZone
;
269 * Set up an environment in which tests that use local services can execute.
271 * @return {@code this} (for chaining)
273 public final LocalServiceTestHelper
setUp() {
274 originalDefaultTimeZone
= TimeZone
.getDefault();
275 TimeZone
.setDefault(timeZone
);
277 ApiProxy
.setEnvironmentForCurrentThread(newEnvironment());
279 apiProxyLocal
= new ApiProxyLocalFactory().create(newLocalServerEnvironment());
281 apiProxyLocal
.setClock(clock
);
283 ApiProxy
.setDelegate(apiProxyLocal
);
285 for (LocalServiceTestConfig config
: configs
) {
292 * Constructs the {@link ApiProxy.Environment} that will be installed.
293 * Subclass and override to provide your own implementation.
295 protected ApiProxy
.Environment
newEnvironment() {
296 LocalEnvironment env
= new LocalEnvironment(envAppId
, envVersionId
) {
298 public String
getEmail() {
303 public boolean isLoggedIn() {
304 return envIsLoggedIn
;
308 public boolean isAdmin() {
313 public String
getAuthDomain() {
314 return envAuthDomain
;
318 public long getRemainingMillis() {
319 return timer
.getRemainingMillis();
322 env
.getAttributes().putAll(envAttributes
);
327 * Constructs a new {@link ApiProxy.Environment} by copying the data from the
328 * given one. The {@code Map} from {@code getAttributes} will be
331 static ApiProxy
.Environment
copyEnvironment(ApiProxy
.Environment copyFrom
){
332 return new TestEnvironment(copyFrom
);
336 * Constructs a new default {@link ApiProxy.Environment}.
338 static ApiProxy
.Environment
newDefaultTestEnvironment() {
339 return new TestEnvironment();
342 private static class TestEnvironment
extends LocalEnvironment
{
343 private String email
;
344 private boolean isLoggedIn
;
345 private boolean isAdmin
;
346 private String authDomain
;
348 private TestEnvironment() {
349 super(DEFAULT_APP_ID
, DEFAULT_VERSION_ID
);
352 private TestEnvironment(String appId
,
353 String majorVersionId
,
358 Map
<String
, Object
> attributes
) {
359 super(appId
, majorVersionId
);
361 this.isLoggedIn
= isLoggedIn
;
362 this.isAdmin
= isAdmin
;
363 this.authDomain
= authDomain
;
364 this.attributes
.putAll(attributes
);
367 public TestEnvironment(ApiProxy
.Environment copyFrom
) {
368 this(copyFrom
.getAppId(),
369 copyFrom
.getVersionId(),
371 copyFrom
.isLoggedIn(),
373 copyFrom
.getAuthDomain(),
374 copyFrom
.getAttributes());
378 public String
getEmail() {
383 public boolean isLoggedIn() {
388 public boolean isAdmin() {
393 public String
getAuthDomain() {
399 * Constructs the {@link LocalServerEnvironment} that will be installed.
400 * Subclass and override to provide your own implementation.
402 protected LocalServerEnvironment
newLocalServerEnvironment() {
403 return new TestLocalServerEnvironment(enforceApiDeadlines
, simulateProdLatencies
);
407 * Tear down the environment in which tests that use local services can
410 public final void tearDown() {
412 RuntimeException firstException
= null;
413 for (LocalServiceTestConfig config
: configs
) {
416 } catch (RuntimeException rte
) {
417 if (firstException
== null) {
418 firstException
= rte
;
422 "Received exception tearing down config of type " + config
.getClass().getName(),
427 if (firstException
!= null) {
428 throw firstException
;
433 ApiProxy
.setDelegate(null);
434 ApiProxy
.setEnvironmentForCurrentThread(null);
435 apiProxyLocal
= null;
437 TimeZone
.setDefault(originalDefaultTimeZone
);
442 * Indicate the request has ended so that local services can do any post request work. This
443 * method is optional and is automatically done in {@link LocalServiceTestHelper#tearDown()}.
444 * You only need to call this method if you want to call {@link LocalServiceTestHelper#tearDown()}
445 * from your own tearDown / @After method but need to end the request to verify any behavior.
447 public static void endRequest() {
448 ((LocalEnvironment
) ApiProxy
.getCurrentEnvironment()).callRequestEndListeners();
452 * Convenience function for getting ahold of the currently
453 * registered {@link ApiProxyLocal}.
455 public static ApiProxyLocal
getApiProxyLocal() {
456 return apiProxyLocal
;
460 * Convenience function for getting ahold of a specific local service.
461 * For example, to get ahold of the LocalDatastoreService you would
462 * call {@code getLocalService(LocalDatastoreService.PACKAGE)}.
464 public static LocalRpcService
getLocalService(String serviceName
) {
465 return getApiProxyLocal().getService(serviceName
);