• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ActivityUnitTestCase(Class<T> activityClass)86     public ActivityUnitTestCase(Class<T> activityClass) {
87         mActivityClass = activityClass;
88     }
89 
90     @Override
getActivity()91     public T getActivity() {
92         return (T) super.getActivity();
93     }
94 
95     @Override
setUp()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      */
startActivity(Intent intent, Bundle savedInstanceState, Object lastNonConfigurationInstance)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
tearDown()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      */
setApplication(Application application)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      */
setActivityContext(Context activityContext)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      */
getRequestedOrientation()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      */
getStartedActivityIntent()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      */
getStartedActivityRequest()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      */
isFinishCalled()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      */
getFinishedActivityRequest()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
setRequestedOrientation(int requestedOrientation)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
getRequestedOrientation()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
getWindow()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
startActivityFromChild(Activity child, Intent intent, int requestCode)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
finishFromChild(Activity child)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
finishActivityFromChild(Activity child, int requestCode)335         public void finishActivityFromChild(Activity child, int requestCode) {
336             mFinished = true;
337             mFinishedActivityRequest = requestCode;
338         }
339     }
340 }
341