blob: 8705a31c9603dd145f9f9994cf5b57a7cd2a68ed [file] [log] [blame] [view]
AndroidX Core Team03b4da32021-03-10 23:20:41 +00001# 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
Ian Baker186108e2023-11-20 06:54:36 -080042[Sample code in Kotlin modules](/docs/api_guidelines/index.md#sample-code-in-kotlin-modules)
AndroidX Core Team03b4da32021-03-10 23:20:41 +000043for information on writing samples that can be referenced from API reference
alanvc326a772022-06-13 07:03:52 -070044documentation or
Ian Baker186108e2023-11-20 06:54:36 -080045[Project directory structure](/docs/api_guidelines/index.md#module-structure)
AndroidX Core Team5fa61982023-01-13 10:43:41 -050046for module naming guidelines if you'd like to create a basic test app.
AndroidX Core Team03b4da32021-03-10 23:20:41 +000047
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
alanvc326a772022-06-13 07:03:52 -070057```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
AndroidX Core Team03b4da32021-03-10 23:20:41 +000076#### 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
alanvc326a772022-06-13 07:03:52 -070086```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
AndroidX Core Team03b4da32021-03-10 23:20:41 +000098If 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
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000115[must also take an Executor](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#provide-executor)
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000116
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
alanvc326a772022-06-13 07:03:52 -0700120with 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.
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000123
alanvc326a772022-06-13 07:03:52 -0700124```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)
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000138
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,
alanvc326a772022-06-13 07:03:52 -0700144the 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```
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000163
164#### Fakes for external dependencies
165
166Sometimes, the developer might want to track side effects of your library for
alanvc326a772022-06-13 07:03:52 -0700167end-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
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000170provide a fake that can record such calls. If you don't think that interface
alanvc326a772022-06-13 07:03:52 -0700171makes 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```
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000191
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
alanvc326a772022-06-13 07:03:52 -0700205emulator). For such cases, you may create APIs that are testing-only to allow
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000206developers 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
alanvc326a772022-06-13 07:03:52 -0700209[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 Team03b4da32021-03-10 23:20:41 +0000210an 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
alanve2c99ad2022-06-30 14:00:30 -0700216When 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
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000233### 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
alanvc326a772022-06-13 07:03:52 -0700244provides
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```
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000263
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
alanvc326a772022-06-13 07:03:52 -0700268[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)
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000277
278## Testability anti-patterns
279
alanvc326a772022-06-13 07:03:52 -0700280When writing integration tests against your code that depends on your library,
281look out for the following anti-patterns.
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000282
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
alanvc326a772022-06-13 07:03:52 -0700299In apps with an active UI animation, the message queue is *never empty*. If the
AndroidX Core Team03b4da32021-03-10 23:20:41 +0000300app 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
Ian Baker186108e2023-11-20 06:54:36 -0800311See [Asynchronous work](/docs/api_guidelines/index.md#async)
AndroidX Core Team5fa61982023-01-13 10:43:41 -0500312in the API Guidelines for more information on exposing the state of asynchronous
313work to clients.
alanvc326a772022-06-13 07:03:52 -0700314
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.