1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.test; 18 19import android.app.Activity; 20import android.app.Application; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.ActivityInfo; 25import android.os.Bundle; 26import android.os.IBinder; 27import android.test.mock.MockApplication; 28import android.view.Window; 29 30/** 31 * This class provides isolated testing of a single activity. The activity under test will 32 * be created with minimal connection to the system infrastructure, and you can inject mocked or 33 * wrappered versions of many of Activity's dependencies. Most of the work is handled 34 * automatically here by {@link #setUp} and {@link #tearDown}. 35 * 36 * <p>If you prefer a functional test, see {@link android.test.ActivityInstrumentationTestCase}. 37 * 38 * <p>It must be noted that, as a true unit test, your Activity will not be running in the 39 * normal system and will not participate in the normal interactions with other Activities. 40 * The following methods should not be called in this configuration - most of them will throw 41 * exceptions: 42 * <ul> 43 * <li>{@link android.app.Activity#createPendingResult(int, Intent, int)}</li> 44 * <li>{@link android.app.Activity#startActivityIfNeeded(Intent, int)}</li> 45 * <li>{@link android.app.Activity#startActivityFromChild(Activity, Intent, int)}</li> 46 * <li>{@link android.app.Activity#startNextMatchingActivity(Intent)}</li> 47 * <li>{@link android.app.Activity#getCallingActivity()}</li> 48 * <li>{@link android.app.Activity#getCallingPackage()}</li> 49 * <li>{@link android.app.Activity#createPendingResult(int, Intent, int)}</li> 50 * <li>{@link android.app.Activity#getTaskId()}</li> 51 * <li>{@link android.app.Activity#isTaskRoot()}</li> 52 * <li>{@link android.app.Activity#moveTaskToBack(boolean)}</li> 53 * <li>{@link android.app.Activity#setPersistent(boolean)}</li> 54 * </ul> 55 * 56 * <p>The following methods may be called but will not do anything. For test purposes, you can use 57 * the methods {@link #getStartedActivityIntent()} and {@link #getStartedActivityRequest()} to 58 * inspect the parameters that they were called with. 59 * <ul> 60 * <li>{@link android.app.Activity#startActivity(Intent)}</li> 61 * <li>{@link android.app.Activity#startActivityForResult(Intent, int)}</li> 62 * </ul> 63 * 64 * <p>The following methods may be called but will not do anything. For test purposes, you can use 65 * the methods {@link #isFinishCalled()} and {@link #getFinishedActivityRequest()} to inspect the 66 * parameters that they were called with. 67 * <ul> 68 * <li>{@link android.app.Activity#finish()}</li> 69 * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li> 70 * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li> 71 * </ul> 72 * 73 */ 74public abstract class ActivityUnitTestCase<T extends Activity> 75 extends ActivityTestCase { 76 77 private Class<T> mActivityClass; 78 79 private Context mActivityContext; 80 private Application mApplication; 81 private MockParent mMockParent; 82 83 private boolean mAttached = false; 84 private boolean mCreated = false; 85 86 public ActivityUnitTestCase(Class<T> activityClass) { 87 mActivityClass = activityClass; 88 } 89 90 @Override 91 public T getActivity() { 92 return (T) super.getActivity(); 93 } 94 95 @Override 96 protected void setUp() throws Exception { 97 super.setUp(); 98 99 // default value for target context, as a default 100 mActivityContext = getInstrumentation().getTargetContext(); 101 } 102 103 /** 104 * Start the activity under test, in the same way as if it was started by 105 * {@link android.content.Context#startActivity Context.startActivity()}, providing the 106 * arguments it supplied. When you use this method to start the activity, it will automatically 107 * be stopped by {@link #tearDown}. 108 * 109 * <p>This method will call onCreate(), but if you wish to further exercise Activity life 110 * cycle methods, you must call them yourself from your test case. 111 * 112 * <p><i>Do not call from your setUp() method. You must call this method from each of your 113 * test methods.</i> 114 * 115 * @param intent The Intent as if supplied to {@link android.content.Context#startActivity}. 116 * @param savedInstanceState The instance state, if you are simulating this part of the life 117 * cycle. Typically null. 118 * @param lastNonConfigurationInstance This Object will be available to the 119 * Activity if it calls {@link android.app.Activity#getLastNonConfigurationInstance()}. 120 * Typically null. 121 * @return Returns the Activity that was created 122 */ 123 protected T startActivity(Intent intent, Bundle savedInstanceState, 124 Object lastNonConfigurationInstance) { 125 assertFalse("Activity already created", mCreated); 126 127 if (!mAttached) { 128 assertNotNull(mActivityClass); 129 setActivity(null); 130 T newActivity = null; 131 try { 132 IBinder token = null; 133 if (mApplication == null) { 134 setApplication(new MockApplication()); 135 } 136 ComponentName cn = new ComponentName(mActivityClass.getPackage().getName(), 137 mActivityClass.getName()); 138 intent.setComponent(cn); 139 ActivityInfo info = new ActivityInfo(); 140 CharSequence title = mActivityClass.getName(); 141 mMockParent = new MockParent(); 142 String id = null; 143 144 newActivity = (T) getInstrumentation().newActivity(mActivityClass, mActivityContext, 145 token, mApplication, intent, info, title, mMockParent, id, 146 lastNonConfigurationInstance); 147 } catch (Exception e) { 148 assertNotNull(newActivity); 149 } 150 151 assertNotNull(newActivity); 152 setActivity(newActivity); 153 154 mAttached = true; 155 } 156 157 T result = getActivity(); 158 if (result != null) { 159 getInstrumentation().callActivityOnCreate(getActivity(), savedInstanceState); 160 mCreated = true; 161 } 162 return result; 163 } 164 165 @Override 166 protected void tearDown() throws Exception { 167 168 setActivity(null); 169 170 // Scrub out members - protects against memory leaks in the case where someone 171 // creates a non-static inner class (thus referencing the test case) and gives it to 172 // someone else to hold onto 173 scrubClass(ActivityInstrumentationTestCase.class); 174 175 super.tearDown(); 176 } 177 178 /** 179 * Set the application for use during the test. You must call this function before calling 180 * {@link #startActivity}. If your test does not call this method, 181 * @param application The Application object that will be injected into the Activity under test. 182 */ 183 public void setApplication(Application application) { 184 mApplication = application; 185 } 186 187 /** 188 * If you wish to inject a Mock, Isolated, or otherwise altered context, you can do so 189 * here. You must call this function before calling {@link #startActivity}. If you wish to 190 * obtain a real Context, as a building block, use getInstrumentation().getTargetContext(). 191 */ 192 public void setActivityContext(Context activityContext) { 193 mActivityContext = activityContext; 194 } 195 196 /** 197 * This method will return the value if your Activity under test calls 198 * {@link android.app.Activity#setRequestedOrientation}. 199 */ 200 public int getRequestedOrientation() { 201 if (mMockParent != null) { 202 return mMockParent.mRequestedOrientation; 203 } 204 return 0; 205 } 206 207 /** 208 * This method will return the launch intent if your Activity under test calls 209 * {@link android.app.Activity#startActivity(Intent)} or 210 * {@link android.app.Activity#startActivityForResult(Intent, int)}. 211 * @return The Intent provided in the start call, or null if no start call was made. 212 */ 213 public Intent getStartedActivityIntent() { 214 if (mMockParent != null) { 215 return mMockParent.mStartedActivityIntent; 216 } 217 return null; 218 } 219 220 /** 221 * This method will return the launch request code if your Activity under test calls 222 * {@link android.app.Activity#startActivityForResult(Intent, int)}. 223 * @return The request code provided in the start call, or -1 if no start call was made. 224 */ 225 public int getStartedActivityRequest() { 226 if (mMockParent != null) { 227 return mMockParent.mStartedActivityRequest; 228 } 229 return 0; 230 } 231 232 /** 233 * This method will notify you if the Activity under test called 234 * {@link android.app.Activity#finish()}, 235 * {@link android.app.Activity#finishFromChild(Activity)}, or 236 * {@link android.app.Activity#finishActivity(int)}. 237 * @return Returns true if one of the listed finish methods was called. 238 */ 239 public boolean isFinishCalled() { 240 if (mMockParent != null) { 241 return mMockParent.mFinished; 242 } 243 return false; 244 } 245 246 /** 247 * This method will return the request code if the Activity under test called 248 * {@link android.app.Activity#finishActivity(int)}. 249 * @return The request code provided in the start call, or -1 if no finish call was made. 250 */ 251 public int getFinishedActivityRequest() { 252 if (mMockParent != null) { 253 return mMockParent.mFinishedActivityRequest; 254 } 255 return 0; 256 } 257 258 /** 259 * This mock Activity represents the "parent" activity. By injecting this, we allow the user 260 * to call a few more Activity methods, including: 261 * <ul> 262 * <li>{@link android.app.Activity#getRequestedOrientation()}</li> 263 * <li>{@link android.app.Activity#setRequestedOrientation(int)}</li> 264 * <li>{@link android.app.Activity#finish()}</li> 265 * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li> 266 * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li> 267 * </ul> 268 * 269 * TODO: Make this overrideable, and the unit test can look for calls to other methods 270 */ 271 private static class MockParent extends Activity { 272 273 public int mRequestedOrientation = 0; 274 public Intent mStartedActivityIntent = null; 275 public int mStartedActivityRequest = -1; 276 public boolean mFinished = false; 277 public int mFinishedActivityRequest = -1; 278 279 /** 280 * Implementing in the parent allows the user to call this function on the tested activity. 281 */ 282 @Override 283 public void setRequestedOrientation(int requestedOrientation) { 284 mRequestedOrientation = requestedOrientation; 285 } 286 287 /** 288 * Implementing in the parent allows the user to call this function on the tested activity. 289 */ 290 @Override 291 public int getRequestedOrientation() { 292 return mRequestedOrientation; 293 } 294 295 /** 296 * By returning null here, we inhibit the creation of any "container" for the window. 297 */ 298 @Override 299 public Window getWindow() { 300 return null; 301 } 302 303 /** 304 * By defining this in the parent, we allow the tested activity to call 305 * <ul> 306 * <li>{@link android.app.Activity#startActivity(Intent)}</li> 307 * <li>{@link android.app.Activity#startActivityForResult(Intent, int)}</li> 308 * </ul> 309 */ 310 @Override 311 public void startActivityFromChild(Activity child, Intent intent, int requestCode) { 312 mStartedActivityIntent = intent; 313 mStartedActivityRequest = requestCode; 314 } 315 316 /** 317 * By defining this in the parent, we allow the tested activity to call 318 * <ul> 319 * <li>{@link android.app.Activity#finish()}</li> 320 * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li> 321 * </ul> 322 */ 323 @Override 324 public void finishFromChild(Activity child) { 325 mFinished = true; 326 } 327 328 /** 329 * By defining this in the parent, we allow the tested activity to call 330 * <ul> 331 * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li> 332 * </ul> 333 */ 334 @Override 335 public void finishActivityFromChild(Activity child, int requestCode) { 336 mFinished = true; 337 mFinishedActivityRequest = requestCode; 338 } 339 } 340} 341