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