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