ActivityUnitTestCase.java revision e70f61b1160e953e5e4d18d30a463fa9ba821779
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
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;
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 */
74public 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);
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
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}
341