xref: /aosp_15_r20/external/robolectric/ARCHITECTURE.md (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1*e6ba1607SAndroid Build Coastguard Worker# Robolectric architecture
2*e6ba1607SAndroid Build Coastguard Worker
3*e6ba1607SAndroid Build Coastguard WorkerRobolectric is a unit testing framework that allows Android code to be tested on
4*e6ba1607SAndroid Build Coastguard Workerthe JVM without the need for an emulator or device. This allows tests to run
5*e6ba1607SAndroid Build Coastguard Workervery quickly in a more hermetic environment. Robolectric has a complex
6*e6ba1607SAndroid Build Coastguard Workerarchitecture and makes use of many advanced features of the JVM such as bytecode
7*e6ba1607SAndroid Build Coastguard Workerinstrumentation and custom ClassLoaders. This document provides a high level
8*e6ba1607SAndroid Build Coastguard Workeroverview of the architecture of Robolectric.
9*e6ba1607SAndroid Build Coastguard Worker
10*e6ba1607SAndroid Build Coastguard Worker# Android framework Jars and instrumentation
11*e6ba1607SAndroid Build Coastguard Worker
12*e6ba1607SAndroid Build Coastguard WorkerAt the heart of Robolectric are the Android framework Jars and the bytecode
13*e6ba1607SAndroid Build Coastguard Workerinstrumentation. The Android framework Jars are a collection of Jar files that
14*e6ba1607SAndroid Build Coastguard Workerare built directly from Android platform sources. There is a single Jar file for
15*e6ba1607SAndroid Build Coastguard Workereach version of Android. These Jar files can be built by checking out an AOSP
16*e6ba1607SAndroid Build Coastguard Workerrepo and building the
17*e6ba1607SAndroid Build Coastguard Worker[robolectric-host-android\_all](https://cs.android.com/android/platform/superproject/main/+/main:external/robolectric/Android.bp;l=99)
18*e6ba1607SAndroid Build Coastguard Workertarget. Unlike the android.jar (stubs jar) files managed by Android Studio,
19*e6ba1607SAndroid Build Coastguard Workerwhich only contain public method signatures, the Robolectric android-all Jars
20*e6ba1607SAndroid Build Coastguard Workercontain the implementation of the Android Java framework. This gives Robolectric
21*e6ba1607SAndroid Build Coastguard Workerthe ability to use as much real Android code as possible. A new android-all jar
22*e6ba1607SAndroid Build Coastguard Workeris uploaded to MavenCentral for each Android release. You can see the current
23*e6ba1607SAndroid Build Coastguard Workerandroid-all jars
24*e6ba1607SAndroid Build Coastguard Worker[here](https://repo1.maven.org/maven2/org/robolectric/android-all/).
25*e6ba1607SAndroid Build Coastguard Worker
26*e6ba1607SAndroid Build Coastguard WorkerHowever, the pristine android-all jars are not the ones used during tests.
27*e6ba1607SAndroid Build Coastguard WorkerInstead, Robolectric modifies the pristine android-all jars using bytecode
28*e6ba1607SAndroid Build Coastguard Workerinstrumentation (see
29*e6ba1607SAndroid Build Coastguard Worker[ClassInstrumentor](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java)).
30*e6ba1607SAndroid Build Coastguard WorkerIt performs several modifications:
31*e6ba1607SAndroid Build Coastguard Worker
32*e6ba1607SAndroid Build Coastguard Worker1. All Android methods, including constructors and static initializers, are
33*e6ba1607SAndroid Build Coastguard Worker   modified to support `shadowing`. This allows any method call to the Android
34*e6ba1607SAndroid Build Coastguard Worker   framework to be intercepted by Robolectric and delegated to a shadow method.
35*e6ba1607SAndroid Build Coastguard Worker   At a high level, this is done by iterating over each Android method and
36*e6ba1607SAndroid Build Coastguard Worker   converting it into two methods: the original method (but renamed), and the
37*e6ba1607SAndroid Build Coastguard Worker   `invokedynamic delegator` which can optionally invoke shadow methods if they
38*e6ba1607SAndroid Build Coastguard Worker   are available.
39*e6ba1607SAndroid Build Coastguard Worker
40*e6ba1607SAndroid Build Coastguard Worker1. Android constructors are specially modified to create shadow objects, if a
41*e6ba1607SAndroid Build Coastguard Worker   shadow class is bound to the Android class being instantiated.
42*e6ba1607SAndroid Build Coastguard Worker
43*e6ba1607SAndroid Build Coastguard Worker1. Because the Android version of Java core classes (libcore) contain subtle
44*e6ba1607SAndroid Build Coastguard Worker   differences to the JDKs, certain problematic method calls have to be
45*e6ba1607SAndroid Build Coastguard Worker   intercepted and rewritten. See
46*e6ba1607SAndroid Build Coastguard Worker   [AndroidInterceptors](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/interceptors/AndroidInterceptors.java).
47*e6ba1607SAndroid Build Coastguard Worker
48*e6ba1607SAndroid Build Coastguard Worker1. Native methods undergo special instrumentation. Currently native methods are
49*e6ba1607SAndroid Build Coastguard Worker   converted to no-op non-native methods that are shadowable by default.
50*e6ba1607SAndroid Build Coastguard Worker   However, there is now a native variant of each method also created. There is
51*e6ba1607SAndroid Build Coastguard Worker   more details about native code in a section below.
52*e6ba1607SAndroid Build Coastguard Worker
53*e6ba1607SAndroid Build Coastguard Worker1. The `final` keyword is stripped from classes and methods.
54*e6ba1607SAndroid Build Coastguard Worker
55*e6ba1607SAndroid Build Coastguard Worker1. Some bespoke pieces of instrumentation, such as supporting
56*e6ba1607SAndroid Build Coastguard Worker   [SparseArray.set](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java#L201).
57*e6ba1607SAndroid Build Coastguard Worker
58*e6ba1607SAndroid Build Coastguard WorkerThis instrumentation is typically performed when a new release of Robolectric is
59*e6ba1607SAndroid Build Coastguard Workermade. These pre-instrumented Android-all jars are published on MavenCentral. See
60*e6ba1607SAndroid Build Coastguard Workerthe
61*e6ba1607SAndroid Build Coastguard Worker[android-all-instrumented](https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/)
62*e6ba1607SAndroid Build Coastguard Workerpath. They are lazily downloaded and during tests runtime using
63*e6ba1607SAndroid Build Coastguard Worker[MavenArtifactFetcher](https://github.com/robolectric/robolectric/blob/master/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java).
64*e6ba1607SAndroid Build Coastguard Worker
65*e6ba1607SAndroid Build Coastguard WorkerAlthough Robolectric supports shadowing for Android framework classes, it is
66*e6ba1607SAndroid Build Coastguard Workeralso possible for users to perform Robolectric instrumentation for any package
67*e6ba1607SAndroid Build Coastguard Worker(with the exception of built in Java packages). This enables shadowing of
68*e6ba1607SAndroid Build Coastguard Workerarbitrary third-party code.
69*e6ba1607SAndroid Build Coastguard Worker
70*e6ba1607SAndroid Build Coastguard Worker# Shadows
71*e6ba1607SAndroid Build Coastguard Worker
72*e6ba1607SAndroid Build Coastguard WorkerBy default when an Android method is invoked during a Robolectric test, the real
73*e6ba1607SAndroid Build Coastguard WorkerAndroid framework code is invoked. This is because a lot of Android framework
74*e6ba1607SAndroid Build Coastguard Workerclasses are pure Java code (e.g the
75*e6ba1607SAndroid Build Coastguard Worker[Intent](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/Intent.java)
76*e6ba1607SAndroid Build Coastguard Workerclass or the
77*e6ba1607SAndroid Build Coastguard Worker[org.json](https://cs.android.com/android/platform/superproject/main/+/main:libcore/json/src/main/java/org/json/)
78*e6ba1607SAndroid Build Coastguard Workerpackage) and that code can run on the JVM without any modifications needed.
79*e6ba1607SAndroid Build Coastguard Worker
80*e6ba1607SAndroid Build Coastguard WorkerHowever, there are cases where Robolectric needs to intercept and replace
81*e6ba1607SAndroid Build Coastguard WorkerAndroid method calls. This most commonly occurs when Android system service or
82*e6ba1607SAndroid Build Coastguard Workernative methods are invoked. To do this, Robolectric uses a system called Shadow
83*e6ba1607SAndroid Build Coastguard Workerclasses.
84*e6ba1607SAndroid Build Coastguard Worker
85*e6ba1607SAndroid Build Coastguard WorkerShadow classes are Java classes that contain the replacement code of Android
86*e6ba1607SAndroid Build Coastguard Workermethods when they are invoked. Each shadow class is bound to specific Android
87*e6ba1607SAndroid Build Coastguard Workerclasses and methods through annotations. There are currently hundreds of shadow
88*e6ba1607SAndroid Build Coastguard Workerclasses that can be found
89*e6ba1607SAndroid Build Coastguard Worker[here](https://github.com/robolectric/robolectric/tree/master/shadows/framework/src/main/java/org/robolectric/shadows).
90*e6ba1607SAndroid Build Coastguard Worker
91*e6ba1607SAndroid Build Coastguard WorkerShadow classes may optionally contain public apis APIs that can customize the
92*e6ba1607SAndroid Build Coastguard Workerbehavior of the methods they are shadowing.
93*e6ba1607SAndroid Build Coastguard Worker
94*e6ba1607SAndroid Build Coastguard WorkerRobolectric allows tests to specify custom shadows as well to provide user
95*e6ba1607SAndroid Build Coastguard Workerdefined implementation for Android classes.
96*e6ba1607SAndroid Build Coastguard Worker
97*e6ba1607SAndroid Build Coastguard Worker## Shadow Packages and the Robolectric Annotation Processor
98*e6ba1607SAndroid Build Coastguard Worker
99*e6ba1607SAndroid Build Coastguard WorkerThere are two categories of shadows: Robolectric’s built-in shadows that are
100*e6ba1607SAndroid Build Coastguard Workeraggregated using the [Robolectric Annotation Processor
101*e6ba1607SAndroid Build Coastguard Worker(RAP)](https://github.com/robolectric/robolectric/blob/master/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java),
102*e6ba1607SAndroid Build Coastguard Workerand custom shadows that are commonly specified using `@Config(shadows = …)`. RAP
103*e6ba1607SAndroid Build Coastguard Workeris configured to process all of the shadow files that exist in Robolectric’s
104*e6ba1607SAndroid Build Coastguard Workercode. The main shadow package is [framework
105*e6ba1607SAndroid Build Coastguard Workershadows](https://github.com/robolectric/robolectric/tree/master/shadows/framework),
106*e6ba1607SAndroid Build Coastguard Workerwhich contain shadows for the Android framework. There are other shadow packages
107*e6ba1607SAndroid Build Coastguard Workerin Robolectric's code, such as [httpclient
108*e6ba1607SAndroid Build Coastguard Workershadows](https://github.com/robolectric/robolectric/tree/master/shadows/httpclient),
109*e6ba1607SAndroid Build Coastguard Workerbut all of them outside of framework shadows are deprecated. When Robolectric is
110*e6ba1607SAndroid Build Coastguard Workerbuilt, each shadow package is processed by RAP and a
111*e6ba1607SAndroid Build Coastguard Worker[ShadowProvider](https://github.com/robolectric/robolectric/blob/master/shadowapi/src/main/java/org/robolectric/internal/ShadowProvider.java)
112*e6ba1607SAndroid Build Coastguard Workerfile is generated. For example, to see the ShadowProvider for the framework
113*e6ba1607SAndroid Build Coastguard Workershadows, you can run:
114*e6ba1607SAndroid Build Coastguard Worker
115*e6ba1607SAndroid Build Coastguard Worker```sh
116*e6ba1607SAndroid Build Coastguard Worker./gradlew :shadows:framework:assemble
117*e6ba1607SAndroid Build Coastguard Workercat ./shadows/framework/build/generated/src/apt/main/org/robolectric/Shadows.java
118*e6ba1607SAndroid Build Coastguard Worker```
119*e6ba1607SAndroid Build Coastguard Worker
120*e6ba1607SAndroid Build Coastguard WorkerIn this file you will see the class `public class Shadows implements
121*e6ba1607SAndroid Build Coastguard WorkerShadowProvider`.
122*e6ba1607SAndroid Build Coastguard Worker
123*e6ba1607SAndroid Build Coastguard WorkerDuring runtime, Robolectric will use ServiceLoader to detect all shadow packages
124*e6ba1607SAndroid Build Coastguard Workerthat implement ShadowProvider and the shadow classes contained in them.
125*e6ba1607SAndroid Build Coastguard Worker
126*e6ba1607SAndroid Build Coastguard Worker# Sandbox and ClassLoader
127*e6ba1607SAndroid Build Coastguard Worker
128*e6ba1607SAndroid Build Coastguard WorkerBefore a Robolectric test is executed, a
129*e6ba1607SAndroid Build Coastguard Worker[Sandbox](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java)
130*e6ba1607SAndroid Build Coastguard Workermust be initialized. A Sandbox consists of some high-level structures that are
131*e6ba1607SAndroid Build Coastguard Workernecessary to run a Robolectric test. It primarily contains a
132*e6ba1607SAndroid Build Coastguard Worker[SandboxClassLoader](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/SandboxClassLoader.java),
133*e6ba1607SAndroid Build Coastguard Workerwhich is a custom ClassLoader that is bound to a specific instrumented
134*e6ba1607SAndroid Build Coastguard WorkerAndroid-all jar. Sandboxes also contain the ExecutorService that serves as the
135*e6ba1607SAndroid Build Coastguard Workermain thread (UI thread) as well as high-level instrumentation configuration. The
136*e6ba1607SAndroid Build Coastguard WorkerSandboxClassLoader is installed as the default ClassLoader for the test method.
137*e6ba1607SAndroid Build Coastguard WorkerWhen any Android class is requested, SandboxClassLoader will attempt to load the
138*e6ba1607SAndroid Build Coastguard WorkerAndroid class from the instrumented Android-all Jar first. The primary goal of
139*e6ba1607SAndroid Build Coastguard WorkerSandboxClassLoader is to ensure that classes from the android.jar stubs jar are
140*e6ba1607SAndroid Build Coastguard Workernot inadvertently loaded. When classes from the android.jar stubs jar are
141*e6ba1607SAndroid Build Coastguard Workerloaded, attempting to invoke any method on them will result in a
142*e6ba1607SAndroid Build Coastguard Worker`RuntimeException(“Stub!”)` error. Typically the Android stubs jar is on the
143*e6ba1607SAndroid Build Coastguard Workerclass path during a Robolectric test, but it is important not to load classes
144*e6ba1607SAndroid Build Coastguard Workerfrom the stubs jar.
145*e6ba1607SAndroid Build Coastguard Worker
146*e6ba1607SAndroid Build Coastguard Worker# Invokedynamic Delegators and ShadowWrangler
147*e6ba1607SAndroid Build Coastguard Worker
148*e6ba1607SAndroid Build Coastguard WorkerThis section provides more detail for `invokedynamic delegators` that were
149*e6ba1607SAndroid Build Coastguard Workerreferenced in the instrumentation section. For an overview of the
150*e6ba1607SAndroid Build Coastguard Worker`invokedynamic` JVM instructions, you can search for articles or watch [YouTube
151*e6ba1607SAndroid Build Coastguard Workervideos such as this](https://www.youtube.com/watch?v=KhiECfzyVt0).
152*e6ba1607SAndroid Build Coastguard Worker
153*e6ba1607SAndroid Build Coastguard WorkerTo reiterate, for any Android method, Robolectric’s instrumentation adds an
154*e6ba1607SAndroid Build Coastguard Worker`invokedynamic delegator` that is responsible for determining at runtime to
155*e6ba1607SAndroid Build Coastguard Workereither invoke the real Android framework code or a shadow method. The first time
156*e6ba1607SAndroid Build Coastguard Workeran Android method is invoked in a Sandbox, it will result in a call to one of
157*e6ba1607SAndroid Build Coastguard Workerthe bootstrap methods in
158*e6ba1607SAndroid Build Coastguard Worker[InvokeDynamicSupport](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java).
159*e6ba1607SAndroid Build Coastguard WorkerThis will subsequently invoke the
160*e6ba1607SAndroid Build Coastguard Worker[ShadowWrangler.findShadowMethodHandle](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java#L197)
161*e6ba1607SAndroid Build Coastguard Workerto determine if a shadow method exists for the method that is being invoked.  If
162*e6ba1607SAndroid Build Coastguard Workera shadow method is available a MethodHandle to it will be returned. Otherwise a
163*e6ba1607SAndroid Build Coastguard WorkerMethodHandle for the original framework code will be returned.
164*e6ba1607SAndroid Build Coastguard Worker
165*e6ba1607SAndroid Build Coastguard Worker# Test lifecycle
166*e6ba1607SAndroid Build Coastguard Worker
167*e6ba1607SAndroid Build Coastguard WorkerThere is a lot of work done by Robolectric before and after a test is run.
168*e6ba1607SAndroid Build Coastguard WorkerBesides the Sandbox and ClassLoader initialization mentioned above, there is
169*e6ba1607SAndroid Build Coastguard Workeralso extensive Android environment initialization that occurs before each test.
170*e6ba1607SAndroid Build Coastguard WorkerThe high-level class for this is
171*e6ba1607SAndroid Build Coastguard Worker[AndroidTestEnvironment](https://github.com/robolectric/robolectric/blob/master/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java).
172*e6ba1607SAndroid Build Coastguard WorkerThis involves:
173*e6ba1607SAndroid Build Coastguard Worker
174*e6ba1607SAndroid Build Coastguard Worker*   Initializing up the Looper mode (i.e. the scheduler)
175*e6ba1607SAndroid Build Coastguard Worker*   Initializing system and app resources
176*e6ba1607SAndroid Build Coastguard Worker*   Initializing the application context and system context
177*e6ba1607SAndroid Build Coastguard Worker*   Loading the Android manifest for the test
178*e6ba1607SAndroid Build Coastguard Worker*   Creating the Application object used for the test
179*e6ba1607SAndroid Build Coastguard Worker*   Initializing the [display configuration](https://robolectric.org/device-configuration/)
180*e6ba1607SAndroid Build Coastguard Worker*   Setting up the ActivityThread
181*e6ba1607SAndroid Build Coastguard Worker*   Creating app directories
182*e6ba1607SAndroid Build Coastguard Worker
183*e6ba1607SAndroid Build Coastguard WorkerIt is possible for users to extend the test environment setup using
184*e6ba1607SAndroid Build Coastguard Worker[TestEnvironmentLifecyclePlugin](https://github.com/robolectric/robolectric/blob/master/pluginapi/src/main/java/org/robolectric/pluginapi/TestEnvironmentLifecyclePlugin.java).
185*e6ba1607SAndroid Build Coastguard Worker
186*e6ba1607SAndroid Build Coastguard WorkerSimilarly, after each test, many Android classes are reset during
187*e6ba1607SAndroid Build Coastguard Worker[RobolectricTestRunner.finallyAfterTest](https://github.com/robolectric/robolectric/blob/master/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java#L301).
188*e6ba1607SAndroid Build Coastguard WorkerThis will iterate over all shadows and invoke their static `@Resetter` methods.
189*e6ba1607SAndroid Build Coastguard Worker
190*e6ba1607SAndroid Build Coastguard Worker# Plugin System
191*e6ba1607SAndroid Build Coastguard Worker
192*e6ba1607SAndroid Build Coastguard WorkerMany parts of Robolectric can be customized using a plugin system based on
193*e6ba1607SAndroid Build Coastguard WorkerJava’s
194*e6ba1607SAndroid Build Coastguard Worker[ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
195*e6ba1607SAndroid Build Coastguard WorkerThis extensibility is useful when running Robolectric in more constrained
196*e6ba1607SAndroid Build Coastguard Workerenvironments. For example, by default, most of the Robolectric classes are
197*e6ba1607SAndroid Build Coastguard Workerdesigned to work in a Gradle/Android Studio environment. However, there are
198*e6ba1607SAndroid Build Coastguard Workercompanies (such as Google) that use alternate build systems (such as Bazel), and
199*e6ba1607SAndroid Build Coastguard Workerit can be helpful to have the ability to customize the behavior of some core
200*e6ba1607SAndroid Build Coastguard Workermodules.
201*e6ba1607SAndroid Build Coastguard Worker
202*e6ba1607SAndroid Build Coastguard WorkerThe
203*e6ba1607SAndroid Build Coastguard Worker[pluginapi](https://github.com/robolectric/robolectric/tree/master/pluginapi/src)
204*e6ba1607SAndroid Build Coastguard Workersubproject contains many extension points of Robolectric. However, virtually any
205*e6ba1607SAndroid Build Coastguard Workerclass that is loaded by Robolectric’s
206*e6ba1607SAndroid Build Coastguard Worker[Injector](https://github.com/robolectric/robolectric/blob/master/utils/src/main/java/org/robolectric/util/inject/Injector.java)
207*e6ba1607SAndroid Build Coastguard Workerhas the ability to use
208*e6ba1607SAndroid Build Coastguard Worker[PluginFinder](https://github.com/robolectric/robolectric/blob/master/utils/src/main/java/org/robolectric/util/inject/PluginFinder.java),
209*e6ba1607SAndroid Build Coastguard Workerwhich means it can be extended at runtime.
210*e6ba1607SAndroid Build Coastguard Worker
211*e6ba1607SAndroid Build Coastguard WorkerTypically ServiceLoaders plugins can be easily written using the
212*e6ba1607SAndroid Build Coastguard Worker[AutoService](https://github.com/google/auto/tree/main/service) project.
213*e6ba1607SAndroid Build Coastguard Worker
214