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