diff --git a/docs/configuring.md b/docs/configuring.md index e3e1def9a..ff779f9e1 100644 --- a/docs/configuring.md +++ b/docs/configuring.md @@ -6,8 +6,9 @@ Several aspects of Robolectric's behavior can be configured at runtime, using ei ### `@Config` annotation -To configure Robolectric for a single test class or method, use the [`@Config`](javadoc/latest/org/robolectric/annotation/Config.html) annotation on the desired class or method. -Annotations applied on methods take precedence over the ones at the class level. +To configure Robolectric for a single test class or method, use the +[`@Config`][config-documentation] annotation on the desired class or method. Annotations applied on +methods take precedence over the ones at the class level. Base classes are also searched for annotations. So if you find yourself specifying the same values on a large number of tests, you can create a base class and move your `@Config` annotation to that class. @@ -44,11 +45,13 @@ shadows=com.mycompany.ShadowFoo,com.mycompany.ShadowBar ``` > [!NOTE] -> Prior to [Robolectric 3.1.3](https://github.com/robolectric/robolectric/releases/tag/robolectric-3.1.3), only a top-level `robolectric.properties` file may be specified. +> Prior to [Robolectric 3.1.3][robolectric-3.1.3-release], only a top-level `robolectric.properties` +> file may be specified. ### Global Configuration -If you wish to change the default for any configurable value for all your tests, you can provide a [`GlobalConfigProvider`](javadoc/latest/org/robolectric/pluginapi/config/GlobalConfigProvider.html) service implementation. +If you wish to change the default for any configurable value for all your tests, you can provide a +[`GlobalConfigProvider`][global-config-provider] service implementation. ## Configurables @@ -56,8 +59,10 @@ The following examples show how to handle common configuration tasks. For clarit ### Configure SDK Level -By default, Robolectric will run your code against the `targetSdk` specified in your module's `build.gradle`/`build.gradle.kts` or `AndroidManifest.xml` file. -If you want to test your code under a different SDK, you can specify the desired SDK(s) using the [`sdk`](javadoc/latest/org/robolectric/annotation/Config.html#sdk()), [`minSdk`](javadoc/latest/org/robolectric/annotation/Config.html#minSdk()) and [`maxSdk`](javadoc/latest/org/robolectric/annotation/Config.html#maxSdk()) config properties: +By default, Robolectric will run your code against the `targetSdk` specified in your module's +`build.gradle`/`build.gradle.kts` or `AndroidManifest.xml` file. If you want to test your code under +a different SDK, you can specify the desired SDK(s) using the [`sdk`][config-sdk], +[`minSdk`][config-min-sdk] and [`maxSdk`][config-max-sdk] config properties: === "Java" @@ -111,13 +116,16 @@ Note that `sdk` and `minSdk`/`maxSdk` may not be specified in the same `@Config` however, `minSdk` and `maxSdk` may be specified together. If any of them is present, they override any SDK specification from a less-specific configuration location. > [!NOTE] -> Prior to [Robolectric 3.2](https://github.com/robolectric/robolectric/releases/tag/robolectric-3.2), `minSdk` and `maxSdk` are ignored, and [`NEWEST_SDK`](javadoc/latest/org/robolectric/annotation/Config.html#NEWEST_SDK), [`OLDEST_SDK`](javadoc/latest/org/robolectric/annotation/Config.html#OLDEST_SDK), and [`TARGET_SDK`](javadoc/latest/org/robolectric/annotation/Config.html#TARGET_SDK) are not supported. -> Also, only integers corresponding to API levels may be specified in a properties file. +> Prior to [Robolectric 3.2][robolectric-3.2-release], `minSdk` and `maxSdk` are ignored, and +> [`NEWEST_SDK`][config-newest-sdk], [`OLDEST_SDK`][config-oldest-sdk], and +> [`TARGET_SDK`][config-target-sdk] are not supported. Also, only integers corresponding to API +> levels may be specified in a properties file. ### Configure `Application` class -Robolectric will attempt to create an instance of your [`Application`](https://developer.android.com/reference/android/app/Application) class -as specified in the `AndroidManifest`. If you want to provide a custom implementation, you can specify it by setting: +Robolectric will attempt to create an instance of your [`Application`][application-documentation] +class as specified in the `AndroidManifest`. If you want to provide a custom implementation, you can +specify it by setting: === "Java" @@ -189,15 +197,17 @@ Some additional options can be configured globally by setting these system prope | `robolectric.dependency.repo.password` | Password of the repository that you defined in `robolectric.dependency.repo.url`. | `null` | | `robolectric.logging.enabled` | Set to `true` to enable debug logging. | `false` | -Since [Robolectric 4.9.1](https://github.com/robolectric/robolectric/releases/tag/robolectric-4.9.1), you can now add these parameters : +Since [Robolectric 4.9.1][robolectric-4.9.1-release], you can now add these parameters: | Property name | Description | Default value | |-----|-----|-----| | `robolectric.dependency.proxy.host` | Set the host of the proxy to use for the runtime dependencies. | `null` | | `robolectric.dependency.proxy.port` | Set the port number of the proxy to use for the runtime dependencies. | `0` | -When using Gradle, you can configure the System Properties for unit tests with the `android.testOptions.unitTests.all` block (see [here](https://developer.android.com/studio/test/advanced-test-setup#configure-gradle-test-options)). -For example, to override the Maven repository URL and ID to download the runtime dependencies from a repository other than Maven Central: +When using Gradle, you can configure the System Properties for unit tests with the +`android.testOptions.unitTests.all` block (see [here][configure-gradle-test-options]). For example, +to override the Maven repository URL and ID to download the runtime dependencies from a repository +other than Maven Central: === "Groovy" @@ -243,10 +253,36 @@ For example, to override the Maven repository URL and ID to download the runtime ## `ConscryptMode` -Starting with [Robolectric 4.9](https://github.com/robolectric/robolectric/releases/tag/robolectric-4.9), Robolectric can either use Conscrypt and BouncyCastle or just BouncyCastle as the security provider. -In order to migrate tests over time, there is a [`ConscryptMode`](javadoc/latest/org/robolectric/annotation/ConscryptMode.html) annotation that controls whether Conscrypt is loaded as the default security provider with BouncyCastle as backup. - -- If [`ConscryptMode.Mode`](javadoc/latest/org/robolectric/annotation/ConscryptMode.Mode.html) is [`ON`](javadoc/latest/org/robolectric/annotation/ConscryptMode.Mode.html#ON), it will install Conscrypt and BouncyCastle. -- If [`ConscryptMode.Mode`](javadoc/latest/org/robolectric/annotation/ConscryptMode.Mode.html) is [`OFF`](javadoc/latest/org/robolectric/annotation/ConscryptMode.Mode.html#OFF), it will only install BouncyCastle. - -This is closer to to the way that it works on [real android](https://cs.android.com/android/platform/superproject/+/android-13.0.0_r1:libcore/ojluni/src/main/java/java/security/Security.java;l=134-137). Robolectric will search for a requested security primitive from Conscrypt first. If it does not support it, Robolectric will try BouncyCastle second. +Starting with [Robolectric 4.9][robolectric-4.9-release], Robolectric can either use Conscrypt and +BouncyCastle or just BouncyCastle as the security provider. In order to migrate tests over time, +there is a [`ConscryptMode`][conscrypt-mode] annotation that controls whether Conscrypt is loaded as +the default security provider with BouncyCastle as backup. + +- If [`ConscryptMode.Mode`][conscrypt-mode-mode] is [`ON`][conscrypt-mode-mode-on], it will install + Conscrypt and BouncyCastle. +- If [`ConscryptMode.Mode`][conscrypt-mode-mode] is [`OFF`][conscrypt-mode-mode-off], it will only + install BouncyCastle. + +This is closer to the way that it works on [real android][android-security]. Robolectric will search +for a requested security primitive from Conscrypt first. If it does not support it, Robolectric will +try BouncyCastle second. + +[android-security]: https://cs.android.com/android/platform/superproject/+/android-13.0.0_r1:libcore/ojluni/src/main/java/java/security/Security.java;l=134-137 +[application-documentation]: https://developer.android.com/reference/android/app/Application +[config-documentation]: javadoc/latest/org/robolectric/annotation/Config.html +[config-max-sdk]: javadoc/latest/org/robolectric/annotation/Config.html#maxSdk() +[config-min-sdk]: javadoc/latest/org/robolectric/annotation/Config.html#minSdk() +[config-newest-sdk]: javadoc/latest/org/robolectric/annotation/Config.html#NEWEST_SDK +[config-oldest-sdk]: javadoc/latest/org/robolectric/annotation/Config.html#OLDEST_SDK +[config-target-sdk]: javadoc/latest/org/robolectric/annotation/Config.html#TARGET_SDK +[config-sdk]: javadoc/latest/org/robolectric/annotation/Config.html#sdk() +[configure-gradle-test-options]: https://developer.android.com/studio/test/advanced-test-setup#configure-gradle-test-options +[conscrypt-mode]: javadoc/latest/org/robolectric/annotation/ConscryptMode.html +[conscrypt-mode-mode]: javadoc/latest/org/robolectric/annotation/ConscryptMode.Mode.html +[conscrypt-mode-mode-off]: javadoc/latest/org/robolectric/annotation/ConscryptMode.Mode.html#OFF +[conscrypt-mode-mode-on]: javadoc/latest/org/robolectric/annotation/ConscryptMode.Mode.html#ON +[global-config-provider]: javadoc/latest/org/robolectric/pluginapi/config/GlobalConfigProvider.html +[robolectric-3.1.3-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-3.1.3 +[robolectric-3.2-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-3.2 +[robolectric-4.9-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-4.9 +[robolectric-4.9.1-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-4.9.1 diff --git a/docs/custom-test-runner.md b/docs/custom-test-runner.md index 54c5f0ee6..03444a0ea 100644 --- a/docs/custom-test-runner.md +++ b/docs/custom-test-runner.md @@ -5,13 +5,17 @@ hide: # Customizing the Test Runner -There are several situations where you want to customize Robolectric's test runner to perform some operation -before all tests are run, or even before each test method is run. One good example is initializing a dependency -injection framework with a different set of dependencies for your test. Fortunately, Robolectric has a way to -hook into the test lifecycle. If you define an [`Application`](https://developer.android.com/reference/android/app/Application) -class in your `AndroidManifest.xml` file, Robolectric will automatically try and load a test version of your -`Application` class first. +There are several situations where you want to customize Robolectric's test runner to perform some +operation before all tests are run, or even before each test method is run. One good example is +initializing a dependency injection framework with a different set of dependencies for your test. +Fortunately, Robolectric has a way to hook into the test lifecycle. If you define an +[`Application`][application-documentation] class in your `AndroidManifest.xml` file, Robolectric +will automatically try and load a test version of your `Application` class first. ## Hilt -If you're using Hilt, you can check the official [Robolectric testing integration](https://dagger.dev/hilt/robolectric-testing.html) guide from Hilt. +If you're using Hilt, you can check the official +[Robolectric testing integration][dagger-hilt-robolectric] guide from Hilt. + +[application-documentation]: https://developer.android.com/reference/android/app/Application +[dagger-hilt-robolectric]: https://dagger.dev/hilt/robolectric-testing.html diff --git a/docs/device-configuration.md b/docs/device-configuration.md index 7ee58529a..010cd1dd6 100644 --- a/docs/device-configuration.md +++ b/docs/device-configuration.md @@ -1,10 +1,13 @@ # Device Configuration -Robolectric makes it easy to simulate a variety of device configurations. In particular, the properties that make up the [`Configuration`](https://developer.android.com/reference/android/content/res/Configuration) class can be specified at the test method, test class, package, or suite level, as described [here](configuring.md). +Robolectric makes it easy to simulate a variety of device configurations. In particular, the +properties that make up the [`Configuration`][configuration-documentation] class can be specified at +the test method, test class, package, or suite level, as described [here](configuring.md). ## Specifying device configuration -The Android device configuration can be specified using the [`qualifiers`](javadoc/latest/org/robolectric/annotation/Config.html#qualifiers()) [`@Config`](javadoc/latest/org/robolectric/annotation/Config.html) argument: +The Android device configuration can be specified using the [`qualifiers`][config-qualifiers] +[`@Config`][config-documentation] argument: === "Java" @@ -24,7 +27,12 @@ The Android device configuration can be specified using the [`qualifiers`](javad } ``` -From [version 3.6](https://github.com/robolectric/robolectric/releases/tag/robolectric-3.6) on, Robolectric parses the `qualifiers` property according to the rules set forth [here](https://developer.android.com/guide/topics/resources/providing-resources.html#QualifierRules) (but with no preceding directory name), and sets up the Android simulation environment with a corresponding configuration. The system's `Configuration`, [`Display`](https://developer.android.com/reference/android/view/Display) and [`DisplayMetrics`](https://developer.android.com/reference/android/hardware/display/DisplayManager) objects will all reflect the specified configuration, the locale will be set, and appropriate resources will be selected. +From [version 3.6][robolectric-3.6-release] on, Robolectric parses the `qualifiers` property +according to the rules set forth [here][android-resources-qualifier-rules] (but with no preceding +directory name), and sets up the Android simulation environment with a corresponding configuration. +The system's `Configuration`, [`Display`][display-documentation] and +[`DisplayMetrics`][display-metrics-documentation] objects will all reflect the specified +configuration, the locale will be set, and appropriate resources will be selected. For unspecified properties, Robolectric picks consistent values based on the properties that have been specified, or uses default values as follows: @@ -108,7 +116,8 @@ Values for unspecified properties are calculated, and rules are applied, after a ## Changing device configuration -The device configuration can be changed within a test using [`RuntimeEnvironment.setQualifiers()`](javadoc/latest/org/robolectric/RuntimeEnvironment.html#setQualifiers(java.lang.String)): +The device configuration can be changed within a test using +[`RuntimeEnvironment.setQualifiers()`][runtime-environment-set-qualifiers]: === "Java" @@ -142,10 +151,30 @@ The device configuration can be changed within a test using [`RuntimeEnvironment The string parameter to `RuntimeEnvironment.setQualifiers()` has the same rules as `Config.qualifiers`. -Note that `RuntimeEnvironment.setQualifiers()` updates the system and application resources with the new configuration, but does not trigger any action on extant activities or other components. [`ActivityController.configurationChange()`](javadoc/latest/org/robolectric/android/controller/ActivityController.html#configurationChange(android.content.res.Configuration)) can be used to simulate the sequence of events that take place on a device when its configuration changes. +Note that `RuntimeEnvironment.setQualifiers()` updates the system and application resources with the +new configuration, but does not trigger any action on extant activities or other components. +[`ActivityController.configurationChange()`][activity-controller-configuration-change] can be used +to simulate the sequence of events that take place on a device when its configuration changes. -If the [`Activity`](https://developer.android.com/reference/android/app/Activity) is configured to handle the configuration changes, `ActivityController.configurationChange()` will call the `Activity`’s `onConfigurationChanged()` method. If not, `ActivityController` destroys and recreates the `Activity`. +If the [`Activity`][activity-documentation] is configured to handle the configuration changes, +`ActivityController.configurationChange()` will call the `Activity`’s `onConfigurationChanged()` +method. If not, `ActivityController` destroys and recreates the `Activity`. ## Simulating displays -Robolectric allows display properties to be changed during a test using setters on [`ShadowDisplay`](javadoc/latest/org/robolectric/shadows/ShadowDisplay.html). Multiple displays can be simulated using APIs on [`ShadowDisplayManager`](javadoc/latest/org/robolectric/shadows/ShadowDisplayManager.html). +Robolectric allows display properties to be changed during a test using setters on +[`ShadowDisplay`][shadow-display]. Multiple displays can be simulated using APIs on +[`ShadowDisplayManager`][shadow-display-manager]. + +[activity-controller-configuration-change]: javadoc/latest/org/robolectric/android/controller/ActivityController.html#configurationChange(android.content.res.Configuration) +[activity-documentation]: https://developer.android.com/reference/android/app/Activity +[android-resources-qualifier-rules]: https://developer.android.com/guide/topics/resources/providing-resources.html#QualifierRules +[config-documentation]: javadoc/latest/org/robolectric/annotation/Config.html +[config-qualifiers]: javadoc/latest/org/robolectric/annotation/Config.html#qualifiers() +[configuration-documentation]: https://developer.android.com/reference/android/content/res/Configuration +[display-documentation]: https://developer.android.com/reference/android/view/Display +[display-metrics-documentation]: https://developer.android.com/reference/android/util/DisplayMetrics +[robolectric-3.6-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-3.6 +[runtime-environment-set-qualifiers]: javadoc/latest/org/robolectric/RuntimeEnvironment.html#setQualifiers(java.lang.String) +[shadow-display]: javadoc/latest/org/robolectric/shadows/ShadowDisplay.html +[shadow-display-manager]: javadoc/latest/org/robolectric/shadows/ShadowDisplayManager.html diff --git a/docs/extending.md b/docs/extending.md index e8973a308..57f0772e6 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -12,13 +12,19 @@ Using byte code instrumentation, Robolectric is able to weave in cross-platform ### What's in a Name? -Why "Shadow"? Shadow objects are not quite [Proxies](https://en.wikipedia.org/wiki/Proxy_pattern "Proxy pattern - Wikipedia, the free encyclopedia"), not quite [Fakes](https://c2.com/cgi/wiki?FakeObject "Fake Object"), not quite [Mocks or Stubs](https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs "Mocks Aren't Stubs"). Shadows are sometimes hidden, sometimes seen, and can lead you to the real object. At least we didn't call them "sheep", which we were considering. +Why "Shadow"? Shadow objects are not quite [Proxies][proxy-pattern], not quite [Fakes][fake-object], +not quite [Mocks or Stubs][mocks-arent-stubs]. Shadows are sometimes hidden, sometimes seen, and can +lead you to the real object. At least we didn't call them "sheep", which we were considering. ### Shadow Classes -Shadow classes always need a public no-arg constructor so that the Robolectric framework can instantiate them. They are associated to the class that they Shadow with an [`@Implements`](javadoc/latest/org/robolectric/annotation/Implements.html) annotation on the class declaration. +Shadow classes always need a public no-arg constructor so that the Robolectric framework can +instantiate them. They are associated to the class that they Shadow with an +[`@Implements`][implements-documentation] annotation on the class declaration. -Shadow classes should mimic the production classes' inheritance hierarchy. For example, if you are implementing a Shadow for [`ViewGroup`](https://developer.android.com/reference/android/view/ViewGroup), `ShadowViewGroup`, then your Shadow class should extend `ViewGroup`'s superclass' Shadow, `ShadowView`. +Shadow classes should mimic the production classes' inheritance hierarchy. For example, if you are +implementing a Shadow for [`ViewGroup`][view-group-documentation], `ShadowViewGroup`, then your +Shadow class should extend `ViewGroup`'s superclass' Shadow, `ShadowView`. === "Java" @@ -55,7 +61,8 @@ Suppose an application defined the following line of code: Under test, the `ShadowImageView#setImageResource(int resId)` method on the Shadow instance would be invoked. -Shadow methods must be marked with the [`@Implementation`](javadoc/latest/org/robolectric/annotation/Implementation.html) annotation. Robolectric includes a lint test to help ensure this is done correctly. +Shadow methods must be marked with the [`@Implementation`][implementation-documentation] annotation. +Robolectric includes a lint test to help ensure this is done correctly. === "Java" @@ -85,13 +92,19 @@ Robolectric supports shadowing all methods on the original class, including `pri Typically `@Implementation` methods should have the `protected` modifier. The intention is to reduce the API surface area of the Shadows; the test author should always call such methods on the Android framework class directly. -It is important that shadow methods are implemented on the corresponding shadow of the class in which they were originally defined. Otherwise, Robolectric's lookup mechanism will not find them (even if they have been declared on a shadow subclass). For example, the method [`setEnabled()`](https://developer.android.com/reference/android/view/View#setEnabled(boolean)) is defined on [`View`](https://developer.android.com/reference/android/view/View). If a `setEnabled()` method is defined on `ShadowViewGroup` instead of `ShadowView` then it will not be found at run time even when `setEnabled()` is called on an instance of `ViewGroup`. +It is important that shadow methods are implemented on the corresponding shadow of the class in +which they were originally defined. Otherwise, Robolectric's lookup mechanism will not find them +(even if they have been declared on a shadow subclass). For example, the method +[`setEnabled()`][view-set-enabled] is defined on [`View`][view-documentation]. If a `setEnabled()` +method is defined on `ShadowViewGroup` instead of `ShadowView` then it will not be found at run time +even when `setEnabled()` is called on an instance of `ViewGroup`. ### Shadowing Constructors Once a Shadow object is instantiated, Robolectric will look for a method named `__constructor__` and annotated with `@Implementation` which has the same arguments as the constructor that was invoked on the real object. -For instance, if the application code was to invoke the [`TextView`](https://developer.android.com/reference/android/widget/TextView) constructor which receives a [`Context`](https://developer.android.com/reference/android/content/Context): +For instance, if the application code was to invoke the [`TextView`][text-view-documentation] +constructor which receives a [`Context`][context-documentation]: === "Java" @@ -131,7 +144,9 @@ Robolectric would invoke the following `__constructor__` method that receives a ### Getting access to the real instance -Sometimes Shadow classes may want to refer to the object they are shadowing, e.g., to manipulate fields. A Shadow class can achieve this by declaring a field annotated with [`@RealObject`](javadoc/latest/org/robolectric/annotation/RealObject.html): +Sometimes Shadow classes may want to refer to the object they are shadowing, e.g., to manipulate +fields. A Shadow class can achieve this by declaring a field annotated with +[`@RealObject`][real-object-documentation]: === "Java" @@ -161,11 +176,13 @@ Sometimes Shadow classes may want to refer to the object they are shadowing, e.g } ``` -Robolectric will set `realPoint` to the actual instance of [`Point`](https://developer.android.com/reference/android/graphics/Point) before invoking any other methods. +Robolectric will set `realPoint` to the actual instance of [`Point`][point-documentation] before +invoking any other methods. It is important to note that methods called on the real object will still be intercepted and redirected by Robolectric. This does not often matter in test code, but it has important implications for Shadow class implementors. Since the Shadow class inheritance hierarchy does not always mirror that of their associated Android classes, it is sometimes necessary to make calls through these real objects so that the Robolectric runtime will have the opportunity to route them to the correct Shadow class based on the actual class of the object. Otherwise, methods on Shadows of base classes would be unable to access methods on the Shadows of their subclasses. -Methods on your shadow class are able to call through to the Android OS code, using [`Shadow.directlyOn()`](javadoc/latest/org/robolectric/shadow/api/Shadow.html#directlyOn(java.lang.Class,java.lang.String,org.robolectric.util.ReflectionHelpers.ClassParameter...)). +Methods on your shadow class are able to call through to the Android OS code, using +[`Shadow.directlyOn()`][shadow-directly-on]. ## Custom Shadows @@ -219,19 +236,27 @@ methods using `@Implementation` or shadowing constructors using `__constructor__ ### Using a Custom Shadows -Custom Shadows get hooked up to Robolectric using the [`@Config`](javadoc/latest/org/robolectric/annotation/Config.html) annotation on the test class or test method, using the [`shadows`](javadoc/latest/org/robolectric/annotation/Config.html#shadows()) array attribute. To use the `MyShadowBitmap` class mentioned in the previous section, you would annotate the test in question with `@Config(shadows = { MyShadowBitmap.class })`. This causes Robolectric to recognize and use your custom shadow when executing code against the class you shadowed. +Custom Shadows get hooked up to Robolectric using the [`@Config`][config-documentation] annotation +on the test class or test method, using the [`shadows`][config-shadows] array attribute. To use the +`MyShadowBitmap` class mentioned in the previous section, you would annotate the test in question +with `@Config(shadows = { MyShadowBitmap.class })`. This causes Robolectric to recognize and use +your custom shadow when executing code against the class you shadowed. If you would like your custom shadows to be applied to all tests in your suite or a certain package, you can configure shadows through the [`robolectric.properties`](configuring.md#robolectricproperties-file) file. -Note, by default `Shadows.shadowOf()` method will not work with custom shadows. You can instead use [`Shadow.extract()`](javadoc/latest/org/robolectric/shadow/api/Shadow.html#extract(java.lang.Object)) and cast the return value to the custom Shadow class you implemented. +Note, by default `Shadows.shadowOf()` method will not work with custom shadows. You can instead +use [`Shadow.extract()`][shadow-extract] and cast the return value to the custom Shadow class you +implemented. ### Building a library of Custom Shadows. If you find yourself building a library of custom shadows, you should consider running Robolectric's shadow annotation processor on your library of shadows. This provides a number of benefits such as: 1. Generating `shadowOf` methods for each of your shadows. -2. Generating a `ServiceLoader` so your custom shadows are automatically applied if found on the classpath. -3. Invoking any `static` [`@Resetter`](javadoc/latest/org/robolectric/annotation/Resetter.html) methods on teardown to enable you to reset static state. +2. Generating a `ServiceLoader` so your custom shadows are automatically applied if found on the + classpath. +3. Invoking any `static` [`@Resetter`][resetter-documentation] methods on teardown to enable you to + reset static state. 4. Perform additional validation and checking on your shadows. === "Groovy" @@ -255,7 +280,7 @@ If you find yourself building a library of custom shadows, you should consider r === "Kotlin" - When you write your shadows in Kotlin, configure [`kapt`](https://kotlinlang.org/docs/kapt.html): + When you write your shadows in Kotlin, configure [`kapt`][kapt-documentation]: ```kotlin plugins { @@ -277,7 +302,10 @@ If you find yourself building a library of custom shadows, you should consider r ### Limit API surface area of shadows -Since [Robolectric 3.7](https://github.com/robolectric/robolectric/releases/tag/robolectric-3.7) `@Implementation` methods including `__constructor__` methods can be made `protected`. This is desirable as test code has no business calling these methods, by making your `@Implementation` methods protected you encourage test writers to call the public Android APIs instead. +Since [Robolectric 3.7][robolectric-3.7-release] `@Implementation` methods including +`__constructor__` methods can be made `protected`. This is desirable as test code has no business +calling these methods, by making your `@Implementation` methods protected you encourage test writers +to call the public Android APIs instead. ### Don't override `equals`, `hashCode` and `toString` in shadows @@ -289,8 +317,32 @@ Rather than using shadows as glorified argument captors, prefer writing a shadow ### Use caution when shadowing your own code -Robolectric provides a lot of power which requires responsible usage. Shadows are ideal for testing interaction with the Android framework as the framework doesn't support dependency injection and makes liberal use of static code. Before writing custom shadows for your own code, consider if you can't better refactor your code and use a popular mocking library such as [Mockito](https://site.mockito.org/). +Robolectric provides a lot of power which requires responsible usage. Shadows are ideal for testing +interaction with the Android framework as the framework doesn't support dependency injection and +makes liberal use of static code. Before writing custom shadows for your own code, consider if you +can't better refactor your code and use a popular mocking library such as [Mockito][mockito]. ### Support the community Please [contribute](contributing.md) your enhancements to Robolectric. This will help the community and reduce the bloat in your own codebase. + +[config-documentation]: javadoc/latest/org/robolectric/annotation/Config.html +[config-shadows]: javadoc/latest/org/robolectric/annotation/Config.html#shadows() +[context-documentation]: https://developer.android.com/reference/android/content/Context +[fake-object]: https://c2.com/cgi/wiki?FakeObject "Fake Object" +[implementation-documentation]: javadoc/latest/org/robolectric/annotation/Implementation.html +[implements-documentation]: javadoc/latest/org/robolectric/annotation/Implements.html +[kapt-documentation]: https://kotlinlang.org/docs/kapt.html +[mockito]: https://site.mockito.org/ +[mocks-arent-stubs]: https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs "Mocks Aren't Stubs" +[point-documentation]: https://developer.android.com/reference/android/graphics/Point +[proxy-pattern]: https://en.wikipedia.org/wiki/Proxy_pattern "Proxy pattern - Wikipedia, the free encyclopedia" +[real-object-documentation]: javadoc/latest/org/robolectric/annotation/RealObject.html +[resetter-documentation]: javadoc/latest/org/robolectric/annotation/Resetter.html +[robolectric-3.7-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-3.7 +[shadow-directly-on]: javadoc/latest/org/robolectric/shadow/api/Shadow.html#directlyOn(java.lang.Class,java.lang.String,org.robolectric.util.ReflectionHelpers.ClassParameter...) +[shadow-extract]: javadoc/latest/org/robolectric/shadow/api/Shadow.html#extract(java.lang.Object) +[text-view-documentation]: https://developer.android.com/reference/android/widget/TextView +[view-documentation]: https://developer.android.com/reference/android/view/View +[view-group-documentation]: https://developer.android.com/reference/android/view/ViewGroup +[view-set-enabled]: https://developer.android.com/reference/android/view/View#setEnabled(boolean) diff --git a/docs/getting-started.md b/docs/getting-started.md index 3f0b842cb..3e3acb379 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,6 +1,8 @@ # Getting Started -Robolectric works with various build systems, which are documented on this page. If you are starting a new project, we recommend [Gradle](https://gradle.org/) as a first choice, since it is the default build system for Android. +Robolectric works with various build systems, which are documented on this page. If you are starting +a new project, we recommend [Gradle][gradle] as a first choice, since it is the default build system +for Android. ## Building with Gradle @@ -63,9 +65,12 @@ Then, mark your test to run with `RobolectricTestRunner`: ## Building with Bazel -Robolectric works with [Bazel](https://bazel.build) 0.10.0 or higher. Bazel integrates with Robolectric through the `android_local_test` rule. The Robolectric Java/Kotlin test code is the same for a Bazel project as for a Gradle project (see section above). +Robolectric works with [Bazel][bazel] 0.10.0 or higher. Bazel integrates with Robolectric through +the `android_local_test` rule. The Robolectric Java/Kotlin test code is the same for a Bazel project +as for a Gradle project (see section above). -Robolectric needs to be added as a dependency to your Bazel project with [`rules_jvm_external`](https://github.com/bazelbuild/rules_jvm_external). Add the following to your `WORKSPACE` file: +Robolectric needs to be added as a dependency to your Bazel project with +[`rules_jvm_external`][bazel-rules-jvm-external]. Add the following to your `WORKSPACE` file: ```python http_archive( @@ -116,9 +121,11 @@ android_local_test( ``` > [!NOTE] -> These instructions use `robolectric-bazel` 4.12.2 and `rules_jvm_external` 5.3. Please check [`robolectric-bazel`'s latest release](https://github.com/robolectric/robolectric-bazel/releases/latest) for up to date information. +> These instructions use `robolectric-bazel` 4.12.2 and `rules_jvm_external` 5.3. Please check +> [`robolectric-bazel`'s latest release][bazel-latest-release] for up-to-date information. -If you have any question about Bazel integration, we recommend to [check `robolectric-bazel`](https://github.com/robolectric/robolectric-bazel) first, and file an issue there if you need assistance. +If you have any question about Bazel integration, we recommend to [check +`robolectric-bazel`][robolectric-bazel] first, and file an issue there if you need assistance. ## Building with Maven @@ -211,3 +218,9 @@ If you reference resources that are outside of your project (i.e. in an AAR depe * [Build with Buck](https://buckbuild.com/rule/robolectric_test.html) * [Build with Buck2](https://buck2.build/docs/api/rules/#robolectric_test) * [Android's Testing samples](https://github.com/android/testing-samples) + +[bazel]: https://bazel.build +[bazel-latest-release]: https://github.com/robolectric/robolectric-bazel/releases/latest +[bazel-rules-jvm-external]: https://github.com/bazelbuild/rules_jvm_external +[gradle]: https://gradle.org/ +[robolectric-bazel]: https://github.com/robolectric/robolectric-bazel diff --git a/docs/index.md b/docs/index.md index c3d15b7ba..cc7cc048e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -59,13 +59,19 @@ Robolectric is a framework that brings fast and reliable unit tests to Android. Unlike traditional emulator-based Android tests, Robolectric tests run inside a sandbox which allows the Android environment to be precisely configured to the desired conditions for each test, isolates each test from its neighbors, and extends the Android framework with test APIs which provide minute control over the Android framework's behavior and visibility of state for assertions. - While much of the Android framework will work as expected inside a Robolectric test, some Android components' regular behavior doesn't translate well to unit tests: hardware sensors need to be simulated, system services need to be loaded with test fixture data. In those cases, Robolectric provides a [test double](https://en.wikipedia.org/wiki/Test_double) that's suitable for most unit testing scenarios. + While much of the Android framework will work as expected inside a Robolectric test, some + Android components' regular behavior doesn't translate well to unit tests: hardware sensors need + to be simulated, system services need to be loaded with test fixture data. In those cases, + Robolectric provides a [test double][test-double] that's suitable for most unit testing + scenarios. - **No Mocking Frameworks Required** --- - An alternate approach to Robolectric is to use mock frameworks such as [Mockito](https://site.mockito.org/) or to mock out the Android SDK. While this is a valid approach, it often yields tests that are essentially reverse implementations of the application code. + An alternate approach to Robolectric is to use mock frameworks such as [Mockito][mockito] or to + mock out the Android SDK. While this is a valid approach, it often yields tests that are + essentially reverse implementations of the application code. Robolectric allows a test style that is closer to black box testing, making the tests more effective for refactoring and allowing the tests to focus on the behavior of the application instead of the implementation of Android. You can still use a mocking framework along with Robolectric if you like. @@ -82,3 +88,6 @@ Robolectric is a framework that brings fast and reliable unit tests to Android. Robolectric handles inflation of `View`s, resource loading, and lots of other stuff that's implemented in native C code on Android devices. This allows tests to do most things you could do on a real device. You can provide your own implementation for specific SDK methods too, so you could simulate error conditions or real-world sensor behavior, for example. + +[mockito]: https://site.mockito.org/ +[test-double]: https://en.wikipedia.org/wiki/Test_double diff --git a/docs/using-add-on-modules.md b/docs/using-add-on-modules.md index b6bf4a07c..0a2240ac2 100644 --- a/docs/using-add-on-modules.md +++ b/docs/using-add-on-modules.md @@ -3,25 +3,36 @@ In order to reduce the number of external dependencies on the application being tested, Robolectric's shadows are split into various add-on packages. Only shadows for classes provided in the base Android SDK are provided by the main Robolectric module. Additional shadows are provided in dedicated packages. > [!NOTE] -> - [Robolectric 3.4](https://github.com/robolectric/robolectric/releases/tag/robolectric-3.4) doesn't include the `shadows-` prefix in the package name (i.e., `org.robolectric:playservices` and `org.robolectric:httpclient`). +> - [Robolectric 3.4][robolectric-3.4-release] doesn't include the `shadows-` prefix in the package + name (i.e., `org.robolectric:playservices` and `org.robolectric:httpclient`). > - Before Robolectric 3.4, `org.robolectric:playservices` was named `shadow-play-services`. ## Supported packages -| SDK package | Robolectric add-on package | Javadoc | -|-----|-----|-----| -| `androidx.multidex.MultiDex` | [`org.robolectric:shadows-multidex`](https://github.com/robolectric/robolectric/tree/robolectric-4.13/shadows/multidex) | [Javadoc](javadoc/latest/org/robolectric/shadows/multidex/package-summary.html) | -| `com.android.support.multidex` | [`org.robolectric:shadows-multidex`](https://github.com/robolectric/robolectric/tree/robolectric-4.13/shadows/multidex) | [Javadoc](javadoc/latest/org/robolectric/shadows/multidex/package-summary.html) | -| `com.google.android.gms:play-services` | [`org.robolectric:shadows-playservices`](https://github.com/robolectric/robolectric/tree/robolectric-4.13/shadows/playservices) | [Javadoc](javadoc/latest/org/robolectric/shadows/gms/package-summary.html) | +| SDK package | Robolectric add-on package | Javadoc | +|----------------------------------------|-----------------------------------------------------------------------|-----------------------------------------| +| `androidx.multidex.MultiDex` | [`org.robolectric:shadows-multidex`][shadows-multidex-source] | [Javadoc][shadows-multidex-javadoc] | +| `com.android.support.multidex` | [`org.robolectric:shadows-multidex`][shadows-multidex-source] | [Javadoc][shadows-multidex-javadoc] | +| `com.google.android.gms:play-services` | [`org.robolectric:shadows-playservices`][shadows-playservices-source] | [Javadoc][shadows-playservices-javadoc] | ## Deprecated packages -| SDK package | Robolectric add-on package | Javadoc | Comment | -|-----|-----|-----|-----| -| `org.apache.httpcomponents:httpclient` | [`org.robolectric:shadows-httpclient`](https://github.com/robolectric/robolectric/tree/robolectric-4.13/shadows/httpclient) | [Javadoc](javadoc/latest/org/robolectric/shadows/httpclient/package-summary.html) | These shadows are only provided for legacy compatibility. | +| SDK package | Robolectric add-on package | Javadoc | Comment | +|----------------------------------------|-------------------------------------------------------------------|---------------------------------------|-----------------------------------------------------------| +| `org.apache.httpcomponents:httpclient` | [`org.robolectric:shadows-httpclient`][shadows-httpclient-source] | [Javadoc][shadows-httpclient-javadoc] | These shadows are only provided for legacy compatibility. | ## Removed packages -| SDK package | Robolectric add-on package | Comment | -|-----|-----|-----| -| `com.android.support.support-v4` | `org.robolectric:shadows-supportv4` | This package was deprecated in [Robolectric 4.8](https://github.com/robolectric/robolectric/releases/tag/robolectric-4.8), and removed in [Robolectric 4.9](https://github.com/robolectric/robolectric/releases/tag/robolectric-4.9). | +| SDK package | Robolectric add-on package | Comment | +|----------------------------------|-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| `com.android.support.support-v4` | `org.robolectric:shadows-supportv4` | This package was deprecated in [Robolectric 4.8][robolectric-4.8-release], and removed in [Robolectric 4.9][robolectric-4.9-release]. | + +[robolectric-3.4-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-3.4 +[robolectric-4.8-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-4.8 +[robolectric-4.9-release]: https://github.com/robolectric/robolectric/releases/tag/robolectric-4.9 +[shadows-httpclient-javadoc]: javadoc/latest/org/robolectric/shadows/httpclient/package-summary.html +[shadows-httpclient-source]: https://github.com/robolectric/robolectric/tree/master/shadows/httpclient +[shadows-multidex-javadoc]: javadoc/latest/org/robolectric/shadows/multidex/package-summary.html +[shadows-multidex-source]: https://github.com/robolectric/robolectric/tree/master/shadows/multidex +[shadows-playservices-javadoc]: javadoc/latest/org/robolectric/shadows/gms/package-summary.html +[shadows-playservices-source]: https://github.com/robolectric/robolectric/tree/master/shadows/playservices diff --git a/docs/using-qualifiers.md b/docs/using-qualifiers.md index 0cd5f8fb2..25b07e462 100644 --- a/docs/using-qualifiers.md +++ b/docs/using-qualifiers.md @@ -5,11 +5,17 @@ hide: # Using qualified resources -As described [in the Android developer docs](https://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources), resource qualifiers allow you to change how your resources are loaded based on such factors as the language on the device, to the screen size, to whether it is day or night. While these changes are often tedious to test rigorously (every string has a translation for all supported languages), you may find yourself wishing to run tests in different resource qualified contexts. +As described [in the Android developer docs][android-providing-alternative-resources], resource +qualifiers allow you to change how your resources are loaded based on such factors as the language +on the device, to the screen size, to whether it is day or night. While these changes are often +tedious to test rigorously (every string has a translation for all supported languages), you may +find yourself wishing to run tests in different resource qualified contexts. ## Specifying resources in test -Specifying a resource qualifier is quite simple: simply add the desired qualifiers to the [`@Config`](javadoc/latest/org/robolectric/annotation/Config.html) annotation on your test case or test class, depending on whether you would like to change the resource qualifiers for the whole file, or simply one test. +Specifying a resource qualifier is quite simple: simply add the desired qualifiers to the +[`@Config`][config-documentation] annotation on your test case or test class, depending on whether +you would like to change the resource qualifiers for the whole file, or simply one test. Given the following resources: @@ -62,4 +68,9 @@ this Robolectric test would pass, using the Android resource qualifier resolutio } ``` -Multiple qualifiers should be separated by dashes and provided in the order put forth in [this list](https://developer.android.com/guide/topics/resources/providing-resources.html#table2). +Multiple qualifiers should be separated by dashes and provided in the order put forth in +[this list][android-resources-qualifiers-order]. + +[android-providing-alternative-resources]: https://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources +[android-resources-qualifiers-order]: https://developer.android.com/guide/topics/resources/providing-resources.html#table2 +[config-documentation]: javadoc/latest/org/robolectric/annotation/Config.html diff --git a/docs/writing-a-test.md b/docs/writing-a-test.md index 0db24ba94..66adfec9e 100644 --- a/docs/writing-a-test.md +++ b/docs/writing-a-test.md @@ -5,7 +5,7 @@ hide: # Writing Your First Test -Let's say that you have an [`Activity`](https://developer.android.com/reference/android/app/Activity) that represents a welcome screen: +Let's say that you have an [`Activity`][activity-documentation] that represents a welcome screen: === "Java" @@ -57,7 +57,9 @@ Let's say that you have an [`Activity`](https://developer.android.com/reference/ We want to write a test that asserts that when the user clicks on the "Login" button, the app launches the `LoginActivity`. -To achieve this, we can check that the correct [`Intent`](https://developer.android.com/reference/android/content/Intent) is started when the click is performed. Since Robolectric is a unit testing framework, the `LoginActivity` will not actually be started. +To achieve this, we can check that the correct [`Intent`][intent-documentation] is started when the +click is performed. Since Robolectric is a unit testing framework, the `LoginActivity` will not +actually be started. === "Java" @@ -136,3 +138,6 @@ Additional test APIs are accessible as static methods on special classes called // Simulate a new display being plugged into the device ShadowDisplayManager.addDisplay("xlarge-port") ``` + +[activity-documentation]: https://developer.android.com/reference/android/app/Activity +[intent-documentation]: https://developer.android.com/reference/android/content/Intent