• 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  * </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