blob: 52d60a90c121c18021d3eee1dc7856c8ab32aad4 [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
42[Sample code in Kotlin modules](api_guidelines.md#sample-code-in-kotlin-modules)
43for information on writing samples that can be referenced from API reference
44documentation or [Project directory structure](policies.md#directory-structure)
45for module naming guidelines if you'd like to create a basic test app.
46
47### Avoiding side-effects {#side-effects}
48
49#### Ensure proper scoping for your library a.k.a. Avoid Singletons
50
51Singletons are usually bad for tests as they live across different tests,
52opening the gates for possible side-effects between tests. When possible, try to
53avoid using singletons. If it is not possible, consider providing a test
54artifact that will reset the state of the singleton between tests.
55
56#### Side effects due to external resources
57
58Sometimes, your library might be controlling resources on the device in which
59case even if it is not a singleton, it might have side-effects. For instance,
60Room, being a database library, inherently modifies a file on the disk. To allow
61proper isolated testing, Room provides a builder option to create the database
62[in memory](https://developer.android.com/reference/androidx/room/Room#inMemoryDatabaseBuilder\(android.content.Context,%20java.lang.Class%3CT%3E\))
63A possible alternative solution could be to provide a test rule that will
64properly close databases after each test.
65
66If your library needs an inherently singleton resource (e.g. `WorkManager` is a
67wrapper around `JobScheduler` and there is only 1 instance of it provided by the
68system), consider providing a testing artifact. To provide isolation for tests,
69the WorkManager library ships a
70[separate testing artifact](https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing)
71
72### "External" dependencies {#external-dependencies}
73
74#### Allow configuration of external resource dependencies
75
76A common example of this use case is libraries that do multi-threaded
77operations. For Kotlin libraries, this is usually achieved by receiving a
78coroutine context or scope. For Java libraries, it is commonly an `Executor`. If
79you have a case like this, make sure it can be passed as a parameter to your
80library.
81
82NOTE Android API Guidelines require that methods accepting a callback
83[must also take an Executor](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#callbacks-listener)
84
85For example, the Room library allows developers to
86[pass different executors](https://developer.android.com/reference/androidx/room/RoomDatabase.Builder#setQueryExecutor\(java.util.concurrent.Executor\))
87for background query operations. When writing a test, developers can invoke this
88with a custom executor where they can track work completion.
89
90* [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)
91
92If the external resource you require does not make sense as a public API, such
93as a main thread executor, then you can provide a testing artifact which will
94allow setting it. For example, the Lifecycle package depends on the main thread
95executor to function but for an application, customizing it does not make sense
96(as there is only 1 "pre-defined" main thread for an app). For testing purposes,
97the Lifecycle library provides a testing artifact which includes
98[TestRules](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:arch/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java)
99to change them.
100
101#### Fakes for external dependencies
102
103Sometimes, the developer might want to track side effects of your library for
104end to end testing. For instance, if your library provides some functionality
105that might decide to toggle bluetooth, outside developer's direct control, it
106might be a good idea to have an interface for that functionality and also
107provide a fake that can record such calls. If you don't think that interface
108makes sense as a library configuration, you can
109[restrict](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java)
110that interface to your library group and provide a testing artifacts with the
111fake so that developer can observe side effects only in tests while you can
112avoid creating unnecessary APIs.
113
114NOTE There is a fine balance between making a library configurable and making
115configuration a nightmare for the developer. You should try to always have
116defaults for these configurable objects to ensure your library is easy to use
117while still testable when necessary.
118
119### Avoiding the need for private API calls in tests {#private-apis}
120
121#### Provide additional functionality for tests
122
123There are certain situations where it could be useful to provide more APIs that
124only make sense in the scope of testing. For example, a `Lifecycle` class has
125methods that are bound to the main thread but developers may want to have other
126tests that rely on lifecycle but do not run on the main thread (or even on an
127emulator). For such cases, you may create APIs that are testing only to allow
128developers to use them only in tests while giving them the confidence that it
129will behave as close as possible to a real implementation. For the case above,
130`LifecycleRegistry` provides an API to
131[create](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java;l=334)
132an instance of it that will not enforce thread restrictions.
133
134NOTE Even though the implementation referenced above is acceptable, it is always
135better to create such functionality in additional testing artifacts when
136possible.
137
138### Avoiding assumptions in app code for library behavior {#undefined-behavior}
139
140#### Provide fakes for common classes in a `-testing` artifact
141
142In some cases, the developer might need an instance of a class provided by your
143library but does not want to (or cannot) initiate it in tests. Moreover,
144behavior of such classes might not be fully defined for edge cases, making it
145difficult for developers to mock them.
146
147For such cases, it is a good practice to provide a fake implementation out of
148the box that can be controlled in tests. For example, the Lifecycle library
149provides a
150[fake implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt)
151for the `LifecycleOwner` class that can be manipulated in tests to create
152different use cases.
153
154## Document how to test with your library
155
156Even when your library is fully testable, it is often not clear for a developer
157to know which APIs are safe to call in tests and when. Providing guidance on
158[d.android.com](https://d.android.com) will make it much easier for the
159developer to start testing with your library.
160
161## Testability anti-patterns
162
163When writing tests against your APIs, look out for the following anti-patterns.
164
165### Calling `Instrumentation.waitForIdleSync()` as a synchronization barrier
166
167The `waitForIdle()` and `waitForIdleSync()` methods claim to "(Synchronously)
168wait for the application to be idle." and may seem like reasonable options when
169there is no obvious way to observe whether an action has completed; however,
170these methods know nothing about the context of the test and return when the
171main thread's message queue is empty.
172
173```java {.bad}
174view.requestKeyboardFocus();
175Instrumentation.waitForIdleSync();
176sendKeyEvents(view, "1234");
177// There is no guarantee that `view` has keyboard focus yet.
178device.pressEnter();
179```
180
181In apps with an active UI animation, the message queue is _never empty_. If the
182app is waiting for a callback across IPC, the message queue may be empty despite
183the test not reaching the desired state.
184
185In some cases, `waitForIdleSync()` introduces enough of a delay that unrelated
186asynchronous actions happen to have completed by the time the method returns;
187however, this delay is purely coincidental and eventually leads to flakiness.
188
189Instead, find a reliable synchronization barrier that guarantees the expected
190state has been reached or the requested action has been completed. This might
191mean adding listeners, callbacks, `ListenableFuture`s, or `LiveData`.
192
193See [Asynchronous work](api_guidelines.md#async) in the API Guidelines for more
194information on exposing the state of asynchronous work to clients.