AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1 | # Testability |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | ## How to write testable libraries |
| 6 | |
| 7 | When developers use a Jetpack library, it should be easy to write reliable and |
| 8 | automated tests for their own code's functionality. In most cases, tests written |
| 9 | against a library will need to interact with that library in some way -- setting |
| 10 | up preconditions, reaching synchronization points, making calls -- and the |
| 11 | library should provide necessary functionality to enable such tests, either |
| 12 | through public APIs or optional `-testing` artifacts. |
| 13 | |
| 14 | **Testability**, in this document, means how easily and effectively the users of |
| 15 | a library can create tests for apps that use that library. |
| 16 | |
| 17 | NOTE Tests that check the behavior of a library have a different mission than |
| 18 | tests made by app developers using that library; however, library developers may |
| 19 | find themselves in a similar situation when writing tests for sample code. |
| 20 | |
| 21 | Often, the specifics of testability will vary from library to library and there |
| 22 | is no one way of providing it. Some libraries have enough public API surface, |
| 23 | others provide additional testing artifacts (e.g. |
| 24 | [Lifecycle Runtime Testing artifact](https://maven.google.com/web/index.html?q=lifecycle#androidx.lifecycle:lifecycle-runtime-testing)). |
| 25 | |
| 26 | The best way to check if your library is testable is to try to write a sample |
| 27 | app with tests. Unlike regular library tests, these apps will be limited to the |
| 28 | public API surface of the library. |
| 29 | |
| 30 | Keep in mind that a good test for a sample app should have: |
| 31 | |
| 32 | * [No side effects](#side-effects) |
| 33 | * [No dependencies on time / looper (except for UI)](#external-dependencies) |
| 34 | * [No private API calls](#private-apis) |
| 35 | * [No assumptions on undefined library behavior](#undefined-behavior) |
| 36 | |
| 37 | If you are able to write such tests for your library, you are good to go. If you |
| 38 | struggled or found yourself writing duplicate testing code, there is room for |
| 39 | improvement. |
| 40 | |
| 41 | To get started with sample code, see |
Ian Baker | 186108e | 2023-11-20 06:54:36 -0800 | [diff] [blame] | 42 | [Sample code in Kotlin modules](/docs/api_guidelines/index.md#sample-code-in-kotlin-modules) |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 43 | for information on writing samples that can be referenced from API reference |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 44 | documentation or |
Ian Baker | 186108e | 2023-11-20 06:54:36 -0800 | [diff] [blame] | 45 | [Project directory structure](/docs/api_guidelines/index.md#module-structure) |
AndroidX Core Team | 5fa6198 | 2023-01-13 10:43:41 -0500 | [diff] [blame] | 46 | for module naming guidelines if you'd like to create a basic test app. |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 47 | |
| 48 | ### Avoiding side-effects {#side-effects} |
| 49 | |
| 50 | #### Ensure proper scoping for your library a.k.a. Avoid Singletons |
| 51 | |
| 52 | Singletons are usually bad for tests as they live across different tests, |
| 53 | opening the gates for possible side-effects between tests. When possible, try to |
| 54 | avoid using singletons. If it is not possible, consider providing a test |
| 55 | artifact that will reset the state of the singleton between tests. |
| 56 | |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 57 | ```java {.bad} |
| 58 | public class JobQueue { |
| 59 | public static JobQueue getInstance(); |
| 60 | } |
| 61 | ``` |
| 62 | |
| 63 | ```java {.good} |
| 64 | public class JobQueue { |
| 65 | public JobQueue(); |
| 66 | } |
| 67 | ``` |
| 68 | |
| 69 | ```kotlin {.good} |
| 70 | object JobQueueTestUtil { |
| 71 | fun createForTest(): JobQueue |
| 72 | fun resetForTesting(jobQueue: JobQueue) |
| 73 | } |
| 74 | ``` |
| 75 | |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 76 | #### Side effects due to external resources |
| 77 | |
| 78 | Sometimes, your library might be controlling resources on the device in which |
| 79 | case even if it is not a singleton, it might have side-effects. For instance, |
| 80 | Room, being a database library, inherently modifies a file on the disk. To allow |
| 81 | proper isolated testing, Room provides a builder option to create the database |
| 82 | [in memory](https://developer.android.com/reference/androidx/room/Room#inMemoryDatabaseBuilder\(android.content.Context,%20java.lang.Class%3CT%3E\)) |
| 83 | A possible alternative solution could be to provide a test rule that will |
| 84 | properly close databases after each test. |
| 85 | |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 86 | ```java {.good} |
| 87 | public class Camera { |
| 88 | // Sends configuration to the camera hardware, which persists the |
| 89 | // config across app restarts and applies to all camera clients. |
| 90 | public void setConfiguration(Config c); |
| 91 | |
| 92 | // Retrieves the current configuration, which allows clients to |
| 93 | // restore the camera hardware to its prior state after testing. |
| 94 | public Config getConfiguration(); |
| 95 | } |
| 96 | ``` |
| 97 | |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 98 | If your library needs an inherently singleton resource (e.g. `WorkManager` is a |
| 99 | wrapper around `JobScheduler` and there is only 1 instance of it provided by the |
| 100 | system), consider providing a testing artifact. To provide isolation for tests, |
| 101 | the WorkManager library ships a |
| 102 | [separate testing artifact](https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing) |
| 103 | |
| 104 | ### "External" dependencies {#external-dependencies} |
| 105 | |
| 106 | #### Allow configuration of external resource dependencies |
| 107 | |
| 108 | A common example of this use case is libraries that do multi-threaded |
| 109 | operations. For Kotlin libraries, this is usually achieved by receiving a |
| 110 | coroutine context or scope. For Java libraries, it is commonly an `Executor`. If |
| 111 | you have a case like this, make sure it can be passed as a parameter to your |
| 112 | library. |
| 113 | |
| 114 | NOTE Android API Guidelines require that methods accepting a callback |
AndroidX Core Team | 21ccf65 | 2022-04-01 14:53:07 +0000 | [diff] [blame] | 115 | [must also take an Executor](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#provide-executor) |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 116 | |
| 117 | For example, the Room library allows developers to |
| 118 | [pass different executors](https://developer.android.com/reference/androidx/room/RoomDatabase.Builder#setQueryExecutor\(java.util.concurrent.Executor\)) |
| 119 | for background query operations. When writing a test, developers can invoke this |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 120 | with a custom executor where they can track work completion. See |
| 121 | [SuspendingQueryTest](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt;l=672) |
| 122 | in Room's integration test app for implementation details. |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 123 | |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 124 | ```kotlin |
| 125 | val localDatabase = Room.inMemoryDatabaseBuilder( |
| 126 | ApplicationProvider.getApplicationContext(), TestDatabase::class.java |
| 127 | ) |
| 128 | .setQueryExecutor(ArchTaskExecutor.getIOThreadExecutor()) |
| 129 | .setTransactionExecutor(wrappedExecutor) |
| 130 | .build() |
| 131 | |
| 132 | // ... |
| 133 | |
| 134 | wrappedExecutor.awaitTermination(1, TimeUnit.SECONDS) |
| 135 | ``` |
| 136 | |
| 137 | * [sample test](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt;l=672) |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 138 | |
| 139 | If the external resource you require does not make sense as a public API, such |
| 140 | as a main thread executor, then you can provide a testing artifact which will |
| 141 | allow setting it. For example, the Lifecycle package depends on the main thread |
| 142 | executor to function but for an application, customizing it does not make sense |
| 143 | (as there is only 1 "pre-defined" main thread for an app). For testing purposes, |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 144 | the Lifecycle library provides a testing artifact which includes the |
| 145 | [CountingTaskExecutorRule](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:arch/core/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java;l=36) |
| 146 | JUnit test rule to change them. |
| 147 | |
| 148 | ```kotlin |
| 149 | @Rule |
| 150 | @JvmField |
| 151 | val countingTaskExecutorRule = CountingTaskExecutorRule() |
| 152 | |
| 153 | // ... |
| 154 | |
| 155 | @After |
| 156 | fun teardown() { |
| 157 | // At the end of all tests, query executor should |
| 158 | // be idle (e.g. transaction thread released). |
| 159 | countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS) |
| 160 | assertThat(countingTaskExecutorRule.isIdle).isTrue() |
| 161 | } |
| 162 | ``` |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 163 | |
| 164 | #### Fakes for external dependencies |
| 165 | |
| 166 | Sometimes, the developer might want to track side effects of your library for |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 167 | end-to-end testing. For instance, if your library provides some functionality |
| 168 | that might decide to toggle Bluetooth -- outside developer's direct control -- |
| 169 | it might be a good idea to have an interface for that functionality and also |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 170 | provide a fake that can record such calls. If you don't think that interface |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 171 | makes sense as a library configuration, you can use the |
| 172 | [@RestrictTo](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java) |
| 173 | annotation with scope |
| 174 | [LIBRARY_GROUP](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java;l=69) |
| 175 | to restrict usage of that interface to your library group and provide a testing |
| 176 | artifact with the fake so that developer can observe side effects only in tests |
| 177 | while you can avoid creating unnecessary APIs. |
| 178 | |
| 179 | ```kotlin |
| 180 | public class EndpointConnector { |
| 181 | public void discoverEndpoints(Executor e, Consumer<List<Endpoint>> callback); |
| 182 | |
| 183 | @RestrictTo(Scope.LIBRARY_GROUP) |
| 184 | public void setBleInterface(BleInterface bleInterface); |
| 185 | } |
| 186 | |
| 187 | public class EndpointConnectorTestHelper { |
| 188 | public void setBleInterface(EndpointConnector e, BleInterface b); |
| 189 | } |
| 190 | ``` |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 191 | |
| 192 | NOTE There is a fine balance between making a library configurable and making |
| 193 | configuration a nightmare for the developer. You should try to always have |
| 194 | defaults for these configurable objects to ensure your library is easy to use |
| 195 | while still testable when necessary. |
| 196 | |
| 197 | ### Avoiding the need for private API calls in tests {#private-apis} |
| 198 | |
| 199 | #### Provide additional functionality for tests |
| 200 | |
| 201 | There are certain situations where it could be useful to provide more APIs that |
| 202 | only make sense in the scope of testing. For example, a `Lifecycle` class has |
| 203 | methods that are bound to the main thread but developers may want to have other |
| 204 | tests that rely on lifecycle but do not run on the main thread (or even on an |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 205 | emulator). For such cases, you may create APIs that are testing-only to allow |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 206 | developers to use them only in tests while giving them the confidence that it |
| 207 | will behave as close as possible to a real implementation. For the case above, |
| 208 | `LifecycleRegistry` provides an API to |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 209 | [create](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java;l=334) |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 210 | an instance of it that will not enforce thread restrictions. |
| 211 | |
| 212 | NOTE Even though the implementation referenced above is acceptable, it is always |
| 213 | better to create such functionality in additional testing artifacts when |
| 214 | possible. |
| 215 | |
alanv | e2c99ad | 2022-06-30 14:00:30 -0700 | [diff] [blame] | 216 | When writing Android platform APIs, testing-only APIs should be clearly |
| 217 | distinguished from non-test API surface and restricted as necessary to prevent |
| 218 | misuse. In some cases, the `@TestApi` annotation may be appropriate to restrict |
| 219 | usage to CTS tests; however, many platform testing APIs are also useful for app |
| 220 | developers. |
| 221 | |
| 222 | ```java {.good} |
| 223 | class AdSelectionManager { |
| 224 | /** |
| 225 | * Returns testing-specific APIs for this manager. |
| 226 | * |
| 227 | * @throws SecurityException when called from a non-debuggable application |
| 228 | */ |
| 229 | public TestAdSelectionManager getTestAdSelectionManager(); |
| 230 | } |
| 231 | ``` |
| 232 | |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 233 | ### Avoiding assumptions in app code for library behavior {#undefined-behavior} |
| 234 | |
| 235 | #### Provide fakes for common classes in a `-testing` artifact |
| 236 | |
| 237 | In some cases, the developer might need an instance of a class provided by your |
| 238 | library but does not want to (or cannot) initiate it in tests. Moreover, |
| 239 | behavior of such classes might not be fully defined for edge cases, making it |
| 240 | difficult for developers to mock them. |
| 241 | |
| 242 | For such cases, it is a good practice to provide a fake implementation out of |
| 243 | the box that can be controlled in tests. For example, the Lifecycle library |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 244 | provides |
| 245 | [TestLifecycleOwner](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt) |
| 246 | as a fake implementation for the `LifecycleOwner` class that can be manipulated |
| 247 | in tests to create different use cases. |
| 248 | |
| 249 | ```java |
| 250 | private TestLifecycleOwner mOwner = new TestLifecycleOwner( |
| 251 | Lifecycle.State.INITIALIZED, new TestCoroutineDispatcher()); |
| 252 | |
| 253 | @Test |
| 254 | public void testObserverToggle() { |
| 255 | Observer<String> observer = (Observer<String>) mock(Observer.class); |
| 256 | mLiveData.observe(mOwner, observer); |
| 257 | |
| 258 | verify(mActiveObserversChanged, never()).onCall(anyBoolean()); |
| 259 | |
| 260 | // ... |
| 261 | } |
| 262 | ``` |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 263 | |
| 264 | ## Document how to test with your library |
| 265 | |
| 266 | Even when your library is fully testable, it is often not clear for a developer |
| 267 | to know which APIs are safe to call in tests and when. Providing guidance on |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 268 | [d.android.com](https://d.android.com) or in a |
| 269 | [Medium post](https://medium.com/androiddevelopers) will make it much easier for |
| 270 | the developer to start testing with your library. |
| 271 | |
| 272 | Examples of testing guidance: |
| 273 | |
| 274 | - [Integration tests with WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing) |
| 275 | - [Test and debug your Room database](https://developer.android.com/training/data-storage/room/testing-db) |
| 276 | - [Unit-testing LiveData and other common observability problems](https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04) |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 277 | |
| 278 | ## Testability anti-patterns |
| 279 | |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 280 | When writing integration tests against your code that depends on your library, |
| 281 | look out for the following anti-patterns. |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 282 | |
| 283 | ### Calling `Instrumentation.waitForIdleSync()` as a synchronization barrier |
| 284 | |
| 285 | The `waitForIdle()` and `waitForIdleSync()` methods claim to "(Synchronously) |
| 286 | wait for the application to be idle." and may seem like reasonable options when |
| 287 | there is no obvious way to observe whether an action has completed; however, |
| 288 | these methods know nothing about the context of the test and return when the |
| 289 | main thread's message queue is empty. |
| 290 | |
| 291 | ```java {.bad} |
| 292 | view.requestKeyboardFocus(); |
| 293 | Instrumentation.waitForIdleSync(); |
| 294 | sendKeyEvents(view, "1234"); |
| 295 | // There is no guarantee that `view` has keyboard focus yet. |
| 296 | device.pressEnter(); |
| 297 | ``` |
| 298 | |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 299 | In apps with an active UI animation, the message queue is *never empty*. If the |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 300 | app is waiting for a callback across IPC, the message queue may be empty despite |
| 301 | the test not reaching the desired state. |
| 302 | |
| 303 | In some cases, `waitForIdleSync()` introduces enough of a delay that unrelated |
| 304 | asynchronous actions happen to have completed by the time the method returns; |
| 305 | however, this delay is purely coincidental and eventually leads to flakiness. |
| 306 | |
| 307 | Instead, find a reliable synchronization barrier that guarantees the expected |
| 308 | state has been reached or the requested action has been completed. This might |
| 309 | mean adding listeners, callbacks, `ListenableFuture`s, or `LiveData`. |
| 310 | |
Ian Baker | 186108e | 2023-11-20 06:54:36 -0800 | [diff] [blame] | 311 | See [Asynchronous work](/docs/api_guidelines/index.md#async) |
AndroidX Core Team | 5fa6198 | 2023-01-13 10:43:41 -0500 | [diff] [blame] | 312 | in the API Guidelines for more information on exposing the state of asynchronous |
| 313 | work to clients. |
alanv | c326a77 | 2022-06-13 07:03:52 -0700 | [diff] [blame] | 314 | |
| 315 | ### Calling `Thread.sleep()` as a synchronization barrier |
| 316 | |
| 317 | `Thread.sleep()` is a common source of flakiness and instability in tests. If a |
| 318 | developer needs to call `Thread.sleep()` -- even indirectly via a |
| 319 | `PollingCheck` -- to get their test into a suitable state for checking |
| 320 | assertions, your library needs to provide more reliable synchronization |
| 321 | barriers. |
| 322 | |
| 323 | ```java {.bad} |
| 324 | List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize); |
| 325 | mPlayer.setPlaylist(playlist); |
| 326 | |
| 327 | // Wait some time for setting the playlist. |
| 328 | Thread.sleep(TIMEOUT_MS); |
| 329 | |
| 330 | assertTrue(mPlayer.getPositionInPlaylist(), 0); |
| 331 | ``` |
| 332 | |
| 333 | See the previous header for more information of providing synchronization |
| 334 | barriers. |