The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | * <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 | */ |
| 74 | public 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); |
Brett Chabot | ff51fe2 | 2009-04-02 09:56:38 -0700 | [diff] [blame] | 139 | ActivityInfo info = new ActivityInfo(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | } |