blob: 40cca90d1b42fda4600928d37e29ce90bd59fbfc [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
Filip Gruszczynskibc7f4f02014-09-29 14:05:19 -070029import android.util.Log;
30
31
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
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>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 * </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 */
76public abstract class ActivityUnitTestCase<T extends Activity>
77 extends ActivityTestCase {
78
Filip Gruszczynskibc7f4f02014-09-29 14:05:19 -070079 private static final String TAG = "ActivityUnitTestCase";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 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
89 public ActivityUnitTestCase(Class<T> activityClass) {
90 mActivityClass = activityClass;
91 }
92
93 @Override
94 public T getActivity() {
95 return (T) super.getActivity();
96 }
97
98 @Override
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 */
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 }
Filip Gruszczynskibc7f4f02014-09-29 14:05:19 -0700139 ComponentName cn = new ComponentName(mActivityClass.getPackage().getName(),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 mActivityClass.getName());
141 intent.setComponent(cn);
Brett Chabotff51fe22009-04-02 09:56:38 -0700142 ActivityInfo info = new ActivityInfo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 CharSequence title = mActivityClass.getName();
144 mMockParent = new MockParent();
145 String id = null;
Filip Gruszczynskibc7f4f02014-09-29 14:05:19 -0700146
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 newActivity = (T) getInstrumentation().newActivity(mActivityClass, mActivityContext,
148 token, mApplication, intent, info, title, mMockParent, id,
149 lastNonConfigurationInstance);
150 } catch (Exception e) {
Filip Gruszczynskibc7f4f02014-09-29 14:05:19 -0700151 Log.w(TAG, "Catching exception", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 assertNotNull(newActivity);
153 }
Filip Gruszczynskibc7f4f02014-09-29 14:05:19 -0700154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 assertNotNull(newActivity);
156 setActivity(newActivity);
157
158 mAttached = true;
159 }
Filip Gruszczynskibc7f4f02014-09-29 14:05:19 -0700160
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 T result = getActivity();
162 if (result != null) {
163 getInstrumentation().callActivityOnCreate(getActivity(), savedInstanceState);
164 mCreated = true;
165 }
166 return result;
167 }
168
169 @Override
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
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
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
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
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
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
339 public void finishActivityFromChild(Activity child, int requestCode) {
340 mFinished = true;
341 mFinishedActivityRequest = requestCode;
342 }
343 }
344}