summaryrefslogtreecommitdiffstats
path: root/test-runner/src/android/test/ServiceTestCase.java
blob: eaace2799de9d5b1f9538b4abcfd05d5d8f5cf5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.test;

import android.app.Application;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.test.mock.MockApplication;

import java.lang.reflect.Field;
import java.util.Random;

/**
 * This test case provides a framework in which you can test Service classes in
 * a controlled environment.  It provides basic support for the lifecycle of a
 * Service, and hooks with which you can inject various dependencies and control
 * the environment in which your Service is tested.
 *
 * <div class="special reference">
 * <h3>Developer Guides</h3>
 * <p>For more information about application testing, read the
 * <a href="{@docRoot}guide/topics/testing/index.html">Testing</a> developer guide.</p>
 * </div>
 *
 * <p><b>Lifecycle Support.</b>
 * A Service is accessed with a specific sequence of
 * calls, as described in the
 * <a href="http://developer.android.com/guide/topics/fundamentals/services.html">Services</a>
 * document. In order to support the lifecycle of a Service,
 * <code>ServiceTestCase</code> enforces this protocol:
 *
 * <ul>
 *      <li>
 *          The {@link #setUp()} method is called before each test method. The base implementation
 *          gets the system context. If you override <code>setUp()</code>, you must call
 *          <code>super.setUp()</code> as the first statement in your override.
 *      </li>
 *      <li>
 *          The test case waits to call {@link android.app.Service#onCreate()} until one of your
 *          test methods calls {@link #startService} or {@link #bindService}.  This gives you an
 *          opportunity to set up or adjust any additional framework or test logic before you test
 *          the running service.
 *      </li>
 *      <li>
 *          When one of your test methods calls {@link #startService ServiceTestCase.startService()}
 *          or {@link #bindService  ServiceTestCase.bindService()}, the test case calls
 *          {@link android.app.Service#onCreate() Service.onCreate()} and then calls either
 *          {@link android.app.Service#startService(Intent) Service.startService(Intent)} or
 *          {@link android.app.Service#bindService(Intent, ServiceConnection, int)
 *          Service.bindService(Intent, ServiceConnection, int)}, as appropriate. It also stores
 *          values needed to track and support the lifecycle.
 *      </li>
 *      <li>
 *          After each test method finishes, the test case calls the {@link #tearDown} method. This
 *          method stops and destroys the service with the appropriate calls, depending on how the
 *          service was started. If you override <code>tearDown()</code>, your must call the
 *          <code>super.tearDown()</code> as the last statement in your override.
 *      </li>
 * </ul>
 *
 * <p>
 *      <strong>Dependency Injection.</strong>
 *      A service has two inherent dependencies, its {@link android.content.Context Context} and its
 *      associated {@link android.app.Application Application}. The ServiceTestCase framework
 *      allows you to inject modified, mock, or isolated replacements for these dependencies, and
 *      thus perform unit tests with controlled dependencies in an isolated environment.
 * </p>
 * <p>
 *      By default, the test case is injected with a full system context and a generic
 *      {@link android.test.mock.MockApplication MockApplication} object. You can inject
 *      alternatives to either of these by invoking
 *      {@link AndroidTestCase#setContext(Context) setContext()} or
 *      {@link #setApplication setApplication()}.  You must do this <em>before</em> calling
 *      startService() or bindService().  The test framework provides a
 *      number of alternatives for Context, including
 *      {@link android.test.mock.MockContext MockContext},
 *      {@link android.test.RenamingDelegatingContext RenamingDelegatingContext},
 *      {@link android.content.ContextWrapper ContextWrapper}, and
 *      {@link android.test.IsolatedContext}.
 */
public abstract class ServiceTestCase<T extends Service> extends AndroidTestCase {

    Class<T> mServiceClass;

    private Context mSystemContext;
    private Application mApplication;

    /**
     * Constructor
     * @param serviceClass The type of the service under test.
     */
    public ServiceTestCase(Class<T> serviceClass) {
        mServiceClass = serviceClass;
    }

    private T mService;
    private boolean mServiceAttached = false;
    private boolean mServiceCreated = false;
    private boolean mServiceStarted = false;
    private boolean mServiceBound = false;
    private Intent mServiceIntent = null;
    private int mServiceId;

    /**
     * @return An instance of the service under test. This instance is created automatically when
     * a test calls {@link #startService} or {@link #bindService}.
     */
    public T getService() {
        return mService;
    }

    /**
     * Gets the current system context and stores it.
     *
     * Extend this method to do your own test initialization. If you do so, you
     * must call <code>super.setUp()</code> as the first statement in your override. The method is
     * called before each test method is executed.
     */
    @Override
    protected void setUp() throws Exception {
        super.setUp();

        // get the real context, before the individual tests have a chance to muck with it
        mSystemContext = getContext();

    }

    /**
     * Creates the service under test and attaches all injected dependencies
     * (Context, Application) to it.  This is called automatically by {@link #startService} or
     * by {@link #bindService}.
     * If you need to call {@link AndroidTestCase#setContext(Context) setContext()} or
     * {@link #setApplication setApplication()}, do so before calling this method.
     */
    protected void setupService() {
        mService = null;
        try {
            mService = mServiceClass.newInstance();
        } catch (Exception e) {
            assertNotNull(mService);
        }
        if (getApplication() == null) {
            setApplication(new MockApplication());
        }
        mService.attach(
                getContext(),
                null,               // ActivityThread not actually used in Service
                mServiceClass.getName(),
                null,               // token not needed when not talking with the activity manager
                getApplication(),
                null                // mocked services don't talk with the activity manager
                );

        assertNotNull(mService);

        mServiceId = new Random().nextInt();
        mServiceAttached = true;
    }

    /**
     * Starts the service under test, in the same way as if it were started by
     * {@link android.content.Context#startService(Intent) Context.startService(Intent)} with
     * an {@link android.content.Intent} that identifies a service.
     * If you use this method to start the service, it is automatically stopped by
     * {@link #tearDown}.
     *
     * @param intent An Intent that identifies a service, of the same form as the Intent passed to
     * {@link android.content.Context#startService(Intent) Context.startService(Intent)}.
     */
    protected void startService(Intent intent) {
        if (!mServiceAttached) {
            setupService();
        }
        assertNotNull(mService);

        if (!mServiceCreated) {
            mService.onCreate();
            mServiceCreated = true;
        }
        mService.onStartCommand(intent, 0, mServiceId);

        mServiceStarted = true;
    }

    /**
     * <p>
     *      Starts the service under test, in the same way as if it were started by
     *      {@link android.content.Context#bindService(Intent, ServiceConnection, int)
     *      Context.bindService(Intent, ServiceConnection, flags)} with an
     *      {@link android.content.Intent} that identifies a service.
     * </p>
     * <p>
     *      Notice that the parameters are different. You do not provide a
     *      {@link android.content.ServiceConnection} object or the flags parameter. Instead,
     *      you only provide the Intent. The method returns an object whose type is a
     *      subclass of {@link android.os.IBinder}, or null if the method fails. An IBinder
     *      object refers to a communication channel between the application and
     *      the service. The flag is assumed to be {@link android.content.Context#BIND_AUTO_CREATE}.
     * </p>
     * <p>
     *      See <a href="{@docRoot}guide/developing/tools/aidl.html">Designing a Remote Interface
     *      Using AIDL</a> for more information about the communication channel object returned
     *      by this method.
     * </p>
     * Note:  To be able to use bindService in a test, the service must implement getService()
     * method. An example of this is in the ApiDemos sample application, in the
     * LocalService demo.
     *
     * @param intent An Intent object of the form expected by
     * {@link android.content.Context#bindService}.
     *
     * @return An object whose type is a subclass of IBinder, for making further calls into
     * the service.
     */
    protected IBinder bindService(Intent intent) {
        if (!mServiceAttached) {
            setupService();
        }
        assertNotNull(mService);

        if (!mServiceCreated) {
            mService.onCreate();
            mServiceCreated = true;
        }
        // no extras are expected by unbind
        mServiceIntent = intent.cloneFilter();
        IBinder result = mService.onBind(intent);

        mServiceBound = true;
        return result;
    }

    /**
     * Makes the necessary calls to stop (or unbind) the service under test, and
     * calls onDestroy().  Ordinarily this is called automatically (by {@link #tearDown}, but
     * you can call it directly from your test in order to check for proper shutdown behavior.
     */
    protected void shutdownService() {
        if (mServiceStarted) {
            mService.stopSelf();
            mServiceStarted = false;
        } else if (mServiceBound) {
            mService.onUnbind(mServiceIntent);
            mServiceBound = false;
        }
        if (mServiceCreated) {
            mService.onDestroy();
        }
    }

    /**
     * <p>
     *      Shuts down the service under test.  Ensures all resources are cleaned up and
     *      garbage collected before moving on to the next test. This method is called after each
     *      test method.
     * </p>
     * <p>
     *      Subclasses that override this method must call <code>super.tearDown()</code> as their
     *      last statement.
     * </p>
     *
     * @throws Exception
     */
    @Override
    protected void tearDown() throws Exception {
        shutdownService();
        mService = null;

        // Scrub out members - protects against memory leaks in the case where someone
        // creates a non-static inner class (thus referencing the test case) and gives it to
        // someone else to hold onto
        scrubClass(ServiceTestCase.class);

        super.tearDown();
    }

    /**
     * Sets the application that is used during the test.  If you do not call this method,
     * a new {@link android.test.mock.MockApplication MockApplication} object is used.
     *
     * @param application The Application object that is used by the service under test.
     *
     * @see #getApplication()
     */
    public void setApplication(Application application) {
        mApplication = application;
    }

    /**
     * Returns the Application object in use by the service under test.
     *
     * @return The application object.
     *
     * @see #setApplication
     */
    public Application getApplication() {
        return mApplication;
    }

    /**
     * Returns the real system context that is saved by {@link #setUp()}. Use it to create
     * mock or other types of context objects for the service under test.
     *
     * @return A normal system context.
     */
    public Context getSystemContext() {
        return mSystemContext;
    }

    /**
     * Tests that {@link #setupService()} runs correctly and issues an
     * {@link junit.framework.Assert#assertNotNull(String, Object)} if it does.
     * You can override this test method if you wish.
     *
     * @throws Exception
     */
    public void testServiceTestCaseSetUpProperly() throws Exception {
        setupService();
        assertNotNull("service should be launched successfully", mService);
    }
}