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