1# Testability 2 3[TOC] 4 5## How to write testable libraries 6 7When developers use a Jetpack library, it should be easy to write reliable and 8automated tests for their own code's functionality. In most cases, tests written 9against a library will need to interact with that library in some way -- setting 10up preconditions, reaching synchronization points, making calls -- and the 11library should provide necessary functionality to enable such tests, either 12through public APIs or optional `-testing` artifacts. 13 14**Testability**, in this document, means how easily and effectively the users of 15a library can create tests for apps that use that library. 16 17NOTE Tests that check the behavior of a library have a different mission than 18tests made by app developers using that library; however, library developers may 19find themselves in a similar situation when writing tests for sample code. 20 21Often, the specifics of testability will vary from library to library and there 22is no one way of providing it. Some libraries have enough public API surface, 23others 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 26The best way to check if your library is testable is to try to write a sample 27app with tests. Unlike regular library tests, these apps will be limited to the 28public API surface of the library. 29 30Keep 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 37If you are able to write such tests for your library, you are good to go. If you 38struggled or found yourself writing duplicate testing code, there is room for 39improvement. 40 41To get started with sample code, see 42[Sample code in Kotlin modules](/docs/api_guidelines/index.md#sample-code-in-kotlin-modules) 43for information on writing samples that can be referenced from API reference 44documentation or 45[Project directory structure](/docs/api_guidelines/index.md#module-structure) 46for module naming guidelines if you'd like to create a basic test app. 47 48### Avoiding side-effects {#side-effects} 49 50#### Ensure proper scoping for your library a.k.a. Avoid Singletons 51 52Singletons are usually bad for tests as they live across different tests, 53opening the gates for possible side-effects between tests. When possible, try to 54avoid using singletons. If it is not possible, consider providing a test 55artifact that will reset the state of the singleton between tests. 56 57```java {.bad} 58public class JobQueue { 59 public static JobQueue getInstance(); 60} 61``` 62 63```java {.good} 64public class JobQueue { 65 public JobQueue(); 66} 67``` 68 69```kotlin {.good} 70object JobQueueTestUtil { 71 fun createForTest(): JobQueue 72 fun resetForTesting(jobQueue: JobQueue) 73} 74``` 75 76#### Side effects due to external resources 77 78Sometimes, your library might be controlling resources on the device in which 79case even if it is not a singleton, it might have side-effects. For instance, 80Room, being a database library, inherently modifies a file on the disk. To allow 81proper 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\)) 83A possible alternative solution could be to provide a test rule that will 84properly close databases after each test. 85 86```java {.good} 87public 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 98If your library needs an inherently singleton resource (e.g. `WorkManager` is a 99wrapper around `JobScheduler` and there is only 1 instance of it provided by the 100system), consider providing a testing artifact. To provide isolation for tests, 101the 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 108A common example of this use case is libraries that do multi-threaded 109operations. For Kotlin libraries, this is usually achieved by receiving a 110coroutine context or scope. For Java libraries, it is commonly an `Executor`. If 111you have a case like this, make sure it can be passed as a parameter to your 112library. 113 114NOTE Android API Guidelines require that methods accepting a callback 115[must also take an Executor](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#provide-executor) 116 117For 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\)) 119for background query operations. When writing a test, developers can invoke this 120with 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) 122in Room's integration test app for implementation details. 123 124```kotlin 125val localDatabase = Room.inMemoryDatabaseBuilder( 126 ApplicationProvider.getApplicationContext(), TestDatabase::class.java 127) 128 .setQueryExecutor(ArchTaskExecutor.getIOThreadExecutor()) 129 .setTransactionExecutor(wrappedExecutor) 130 .build() 131 132// ... 133 134wrappedExecutor.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) 138 139If the external resource you require does not make sense as a public API, such 140as a main thread executor, then you can provide a testing artifact which will 141allow setting it. For example, the Lifecycle package depends on the main thread 142executor 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, 144the 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) 146JUnit test rule to change them. 147 148```kotlin 149@Rule 150@JvmField 151val countingTaskExecutorRule = CountingTaskExecutorRule() 152 153// ... 154 155@After 156fun 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``` 163 164#### Fakes for external dependencies 165 166Sometimes, the developer might want to track side effects of your library for 167end-to-end testing. For instance, if your library provides some functionality 168that might decide to toggle Bluetooth -- outside developer's direct control -- 169it might be a good idea to have an interface for that functionality and also 170provide a fake that can record such calls. If you don't think that interface 171makes 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) 173annotation 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) 175to restrict usage of that interface to your library group and provide a testing 176artifact with the fake so that developer can observe side effects only in tests 177while you can avoid creating unnecessary APIs. 178 179```kotlin 180public 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 187public class EndpointConnectorTestHelper { 188 public void setBleInterface(EndpointConnector e, BleInterface b); 189} 190``` 191 192NOTE There is a fine balance between making a library configurable and making 193configuration a nightmare for the developer. You should try to always have 194defaults for these configurable objects to ensure your library is easy to use 195while 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 201There are certain situations where it could be useful to provide more APIs that 202only make sense in the scope of testing. For example, a `Lifecycle` class has 203methods that are bound to the main thread but developers may want to have other 204tests that rely on lifecycle but do not run on the main thread (or even on an 205emulator). For such cases, you may create APIs that are testing-only to allow 206developers to use them only in tests while giving them the confidence that it 207will behave as close as possible to a real implementation. For the case above, 208`LifecycleRegistry` provides an API to 209[create](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java;l=334) 210an instance of it that will not enforce thread restrictions. 211 212NOTE Even though the implementation referenced above is acceptable, it is always 213better to create such functionality in additional testing artifacts when 214possible. 215 216When writing Android platform APIs, testing-only APIs should be clearly 217distinguished from non-test API surface and restricted as necessary to prevent 218misuse. In some cases, the `@TestApi` annotation may be appropriate to restrict 219usage to CTS tests; however, many platform testing APIs are also useful for app 220developers. 221 222```java {.good} 223class 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 233### Avoiding assumptions in app code for library behavior {#undefined-behavior} 234 235#### Provide fakes for common classes in a `-testing` artifact 236 237In some cases, the developer might need an instance of a class provided by your 238library but does not want to (or cannot) initiate it in tests. Moreover, 239behavior of such classes might not be fully defined for edge cases, making it 240difficult for developers to mock them. 241 242For such cases, it is a good practice to provide a fake implementation out of 243the box that can be controlled in tests. For example, the Lifecycle library 244provides 245[TestLifecycleOwner](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt) 246as a fake implementation for the `LifecycleOwner` class that can be manipulated 247in tests to create different use cases. 248 249```java 250private TestLifecycleOwner mOwner = new TestLifecycleOwner( 251 Lifecycle.State.INITIALIZED, new TestCoroutineDispatcher()); 252 253@Test 254public 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``` 263 264## Document how to test with your library 265 266Even when your library is fully testable, it is often not clear for a developer 267to know which APIs are safe to call in tests and when. Providing guidance on 268[d.android.com](https://d.android.com) or in a 269[Medium post](https://medium.com/androiddevelopers) will make it much easier for 270the developer to start testing with your library. 271 272Examples 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) 277 278## Testability anti-patterns 279 280When writing integration tests against your code that depends on your library, 281look out for the following anti-patterns. 282 283### Calling `Instrumentation.waitForIdleSync()` as a synchronization barrier 284 285The `waitForIdle()` and `waitForIdleSync()` methods claim to "(Synchronously) 286wait for the application to be idle." and may seem like reasonable options when 287there is no obvious way to observe whether an action has completed; however, 288these methods know nothing about the context of the test and return when the 289main thread's message queue is empty. 290 291```java {.bad} 292view.requestKeyboardFocus(); 293Instrumentation.waitForIdleSync(); 294sendKeyEvents(view, "1234"); 295// There is no guarantee that `view` has keyboard focus yet. 296device.pressEnter(); 297``` 298 299In apps with an active UI animation, the message queue is *never empty*. If the 300app is waiting for a callback across IPC, the message queue may be empty despite 301the test not reaching the desired state. 302 303In some cases, `waitForIdleSync()` introduces enough of a delay that unrelated 304asynchronous actions happen to have completed by the time the method returns; 305however, this delay is purely coincidental and eventually leads to flakiness. 306 307Instead, find a reliable synchronization barrier that guarantees the expected 308state has been reached or the requested action has been completed. This might 309mean adding listeners, callbacks, `ListenableFuture`s, or `LiveData`. 310 311See [Asynchronous work](/docs/api_guidelines/index.md#async) 312in the API Guidelines for more information on exposing the state of asynchronous 313work to clients. 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 318developer needs to call `Thread.sleep()` -- even indirectly via a 319`PollingCheck` -- to get their test into a suitable state for checking 320assertions, your library needs to provide more reliable synchronization 321barriers. 322 323```java {.bad} 324List<MediaItem> playlist = MediaTestUtils.createPlaylist(playlistSize); 325mPlayer.setPlaylist(playlist); 326 327// Wait some time for setting the playlist. 328Thread.sleep(TIMEOUT_MS); 329 330assertTrue(mPlayer.getPositionInPlaylist(), 0); 331``` 332 333See the previous header for more information of providing synchronization 334barriers. 335