• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1[< Instrumentation](/testing/android/docs/instrumentation.md)
2
3# Creating a component for testing
4
5Creating a component, e.g. a [ContentProvider][1] or [Service][2], for use
6in testing can be tricky. This is particularly true when:
7 - app code and test code are in separate APKs
8 - the test APK instruments the app APK
9 - the test Service depends on app code.
10
11This doc explains the pitfalls in creating a test component and how to
12avoid them.
13
14[TOC]
15
16## What can go wrong with a test component
17
18> **tl;dr:** test components may not be able to access app code when defined in
19> the test APK.
20
21Chromium's instrumentation test suites are all currently set up in the manner
22described above: app code is in one APK (the _APK under test_), test code is
23in another APK (the _test APK_), and auxiliary code, when necessary, is in
24one or more other APKs (_support APKs_). Test APKs build against app code
25but do not retain it in their .dex files. This reduces the size of test APKs
26and avoids potentially conflicting definitions. At instrumentation runtime,
27the test code is loaded into the app package's process, and it's consequently
28able to access code defined in the app APK's .dex file(s). Test components,
29however, run in the test package's process and only have access to code in the
30test APK. While test components referencing app code will build without issue,
31they will fail to link at runtime and will consequently not be able to be
32instantiated.
33
34For example, here's the logcat from an attempt to use a test Service,
35`TestPostMessageService`, that extended an app Service, `PostMessageService`.
36Note that the runtime link failed because the superclass couldn't be resolved,
37and the test Service could not be instantiated as a result.
38
39``` text
40Unable to resolve superclass of Lorg/chromium/chrome/browser/customtabs/TestPostMessageService; (184)
41Link of class 'Lorg/chromium/chrome/browser/customtabs/TestPostMessageService;' failed
42...
43FATAL EXCEPTION: main
44Process: org.chromium.chrome.tests, PID: 30023
45java.lang.RuntimeException:
46    Unable to instantiate service org.chromium.chrome.browser.customtabs.TestPostMessageService:
47        java.lang.ClassNotFoundException:
48        Didn't find class "org.chromium.chrome.browser.customtabs.TestPostMessageService" on path:
49        DexPathList[
50            [zip file "/system/framework/android.test.runner.jar",
51             zip file "/data/app/org.chromium.chrome.tests-1.apk"],
52            nativeLibraryDirectories=[
53                /data/app-lib/org.chromium.chrome.tests-1,
54                /vendor/lib,
55                /system/lib]]
56  at android.app.ActivityThread.handleCreateService(ActivityThread.java:2543)
57  at android.app.ActivityThread.access$1800(ActivityThread.java:135)
58  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
59  at android.os.Handler.dispatchMessage(Handler.java:102)
60  at android.os.Looper.loop(Looper.java:136)
61  at android.app.ActivityThread.main(ActivityThread.java:5001)
62  at java.lang.reflect.Method.invokeNative(Native Method)
63  at java.lang.reflect.Method.invoke(Method.java:515)
64  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
65  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
66  at dalvik.system.NativeStart.main(Native Method)
67Caused by: java.lang.ClassNotFoundException:
68    Didn't find class "org.chromium.chrome.browser.customtabs.TestPostMessageService" on path:
69    DexPathList[
70        [zip file "/system/framework/android.test.runner.jar",
71         zip file "/data/app/org.chromium.chrome.tests-1.apk"],
72        nativeLibraryDirectories=[
73            /data/app-lib/org.chromium.chrome.tests-1,
74            /vendor/lib,
75            /system/lib]]
76  at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
77  at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
78  at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
79  at android.app.ActivityThread.handleCreateService(ActivityThread.java:2540)
80  ... 10 more
81```
82
83## How to implement a test component
84
85There are (at least) two mechanisms for avoiding the failures described above:
86using a support APK or using sharedUserIds.
87
88### Use a support APK
89
90Putting the Service in a support APK lets the build system include all necessary
91code in the .dex without fear of conflicting definitions, as nothing in the
92support APK runs in the same package or process as the test or app code.
93
94To do this:
95
96**Create the component.**
97
98It should either be in an existing directory used by code in the appropriate
99support APK or a new directory for such purpose. In particular, it should be
100in neither a directory containing app code nor a directory containing test
101code.
102
103``` java
104package org.chromium.chrome.test;
105
106import org.chromium.chrome.MyAppService;
107
108public class MyTestService extends MyAppService {
109    ...
110}
111```
112
113**Put the component in a separate gn target.**
114
115This can either be a target upon which the support APK already depends or a new
116target.
117
118``` python
119android_library("my_test_service") {
120  sources = [ "src/org/chromium/chrome/test/MyTestService.java" ]
121  deps = [ ... ]
122}
123```
124
125The support APK must depend on this target. The target containing your test
126code can also depend on this target if you want to refer to the component
127class directly, e.g., when binding a Service.
128
129> **NOTE:** Even if your test code directly depends on the service target,
130> you won't be able to directly reference the test component instance from
131> test code. Checking a test component's internal state will require adding
132> APIs specifically for that purpose.
133
134**Define the component in the support APK manifest.**
135
136``` xml
137<manifest ...
138    package="org.chromium.chrome.tests.support" >
139
140  <application>
141    <service android:name="org.chromium.chrome.test.MyTestService"
142        android:exported="true"
143        tools:ignore="ExportedService">
144      ...
145    </service>
146
147    ...
148  </application>
149</manifest>
150```
151
152### Use a sharedUserId
153
154Using a [sharedUserId][3] will allow your component to run in your app's
155process.
156
157> Because this requires modifying the app manifest, it is not recommended at
158> this time and is intentionally not further documented here.
159
160[1]: https://developer.android.com/reference/android/content/ContentProvider.html
161[2]: https://developer.android.com/reference/android/app/Service.html
162[3]: https://developer.android.com/guide/topics/manifest/manifest-element.html#uid
163