android-sdd 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/index.js +143 -0
  2. package/package.json +27 -0
  3. package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
  4. package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
  5. package/skills/Android Platform/Configuration/SKILL.md +201 -0
  6. package/skills/Android Platform/Filesystem/SKILL.md +216 -0
  7. package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
  8. package/skills/Android Platform/Manifest/SKILL.md +226 -0
  9. package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
  10. package/skills/Android Platform/Resources/SKILL.md +234 -0
  11. package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
  12. package/skills/Android Platform/State Restoration/SKILL.md +210 -0
  13. package/skills/Architecture/Bounded Context/SKILL.md +207 -0
  14. package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
  15. package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
  16. package/skills/Architecture/Entity Design/SKILL.md +243 -0
  17. package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
  18. package/skills/Architecture/MVI/SKILL.md +224 -0
  19. package/skills/Architecture/MVVM/SKILL.md +198 -0
  20. package/skills/Architecture/Modularization/SKILL.md +194 -0
  21. package/skills/Architecture/Offline First/SKILL.md +249 -0
  22. package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
  23. package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
  24. package/skills/Architecture/State Management/SKILL.md +229 -0
  25. package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
  26. package/skills/Architecture/Use Case Design/SKILL.md +244 -0
  27. package/skills/Architecture/Value Object/SKILL.md +226 -0
  28. package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
  29. package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
  30. package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
  31. package/skills/Build System/Build Cache/SKILL.md +233 -0
  32. package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
  33. package/skills/Build System/Build Variant/SKILL.md +215 -0
  34. package/skills/Build System/Convention Plugin/SKILL.md +288 -0
  35. package/skills/Build System/Dependency Management/SKILL.md +261 -0
  36. package/skills/Build System/Gradle/SKILL.md +284 -0
  37. package/skills/Build System/Incremental Build/SKILL.md +199 -0
  38. package/skills/Build System/KAPT/SKILL.md +198 -0
  39. package/skills/Build System/KSP/SKILL.md +263 -0
  40. package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
  41. package/skills/Build System/Specialized/C++/SKILL.md +308 -0
  42. package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
  43. package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
  44. package/skills/Build System/Version Catalog/SKILL.md +304 -0
  45. package/skills/Concurrency/Background Processing/SKILL.md +185 -0
  46. package/skills/Concurrency/Channel/SKILL.md +207 -0
  47. package/skills/Concurrency/Coroutine/SKILL.md +200 -0
  48. package/skills/Concurrency/Flow/SKILL.md +179 -0
  49. package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
  50. package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
  51. package/skills/Concurrency/StateFlow/SKILL.md +175 -0
  52. package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
  53. package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
  54. package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
  55. package/skills/Core Language/DSL/SKILL.md +186 -0
  56. package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
  57. package/skills/Core Language/Immutability/SKILL.md +156 -0
  58. package/skills/Core Language/KMP/SKILL.md +182 -0
  59. package/skills/Core Language/Kotlin/SKILL.md +187 -0
  60. package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
  61. package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
  62. package/skills/Core Language/Serialization/SKILL.md +191 -0
  63. package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
  64. package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
  65. package/skills/Data Layer/DAO/SKILL.md +225 -0
  66. package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
  67. package/skills/Data Layer/DataStore/SKILL.md +264 -0
  68. package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
  69. package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
  70. package/skills/Data Layer/File Storage/SKILL.md +247 -0
  71. package/skills/Data Layer/Indexing/SKILL.md +184 -0
  72. package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
  73. package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
  74. package/skills/Data Layer/Migration/SKILL.md +243 -0
  75. package/skills/Data Layer/Paging/SKILL.md +264 -0
  76. package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
  77. package/skills/Data Layer/Room/SKILL.md +244 -0
  78. package/skills/Data Layer/SQLite/SKILL.md +255 -0
  79. package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
  80. package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
  81. package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
  82. package/skills/Dependency Injection/Koin/SKILL.md +282 -0
  83. package/skills/Developer Experience/Detekt/SKILL.md +272 -0
  84. package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
  85. package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
  86. package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
  87. package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
  88. package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
  89. package/skills/Media/Audio/SKILL.md +257 -0
  90. package/skills/Media/Camera/SKILL.md +229 -0
  91. package/skills/Media/CameraX/SKILL.md +295 -0
  92. package/skills/Media/ExoPlayer/SKILL.md +258 -0
  93. package/skills/Media/Video/SKILL.md +228 -0
  94. package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
  95. package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
  96. package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
  97. package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
  98. package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
  99. package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
  100. package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
  101. package/skills/Navigation/Navigation/SKILL.md +215 -0
  102. package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
  103. package/skills/Networking/API Contract/SKILL.md +220 -0
  104. package/skills/Networking/Authentication/SKILL.md +210 -0
  105. package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
  106. package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
  107. package/skills/Networking/Ktor/SKILL.md +219 -0
  108. package/skills/Networking/Multipart Upload/SKILL.md +213 -0
  109. package/skills/Networking/OkHttp/SKILL.md +193 -0
  110. package/skills/Networking/REST/SKILL.md +178 -0
  111. package/skills/Networking/Rate Limiting/SKILL.md +170 -0
  112. package/skills/Networking/Retrofit/SKILL.md +241 -0
  113. package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
  114. package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
  115. package/skills/Networking/WebSocket/SKILL.md +224 -0
  116. package/skills/Observability/Crash Reporting/SKILL.md +219 -0
  117. package/skills/Observability/Logging/SKILL.md +168 -0
  118. package/skills/Observability/Metrics/SKILL.md +227 -0
  119. package/skills/Observability/Structured Logging/SKILL.md +234 -0
  120. package/skills/Performance/ANR Prevention/SKILL.md +192 -0
  121. package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
  122. package/skills/Performance/App Startup/SKILL.md +183 -0
  123. package/skills/Performance/Baseline Profile/SKILL.md +205 -0
  124. package/skills/Performance/Battery Optimization/SKILL.md +192 -0
  125. package/skills/Performance/Benchmark/SKILL.md +182 -0
  126. package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
  127. package/skills/Performance/Compose Optimization/SKILL.md +187 -0
  128. package/skills/Performance/Heap Management/SKILL.md +184 -0
  129. package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
  130. package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
  131. package/skills/Performance/Rendering Performance/SKILL.md +205 -0
  132. package/skills/Performance/Startup Optimization/SKILL.md +219 -0
  133. package/skills/Security/Biometric/SKILL.md +224 -0
  134. package/skills/Security/Certificate Transparency/SKILL.md +158 -0
  135. package/skills/Security/Cryptography/SKILL.md +244 -0
  136. package/skills/Security/Encrypted Storage/SKILL.md +273 -0
  137. package/skills/Security/Frida Detection/SKILL.md +230 -0
  138. package/skills/Security/Hook Detection/SKILL.md +197 -0
  139. package/skills/Security/Keystore/SKILL.md +272 -0
  140. package/skills/Security/Network Security Config/SKILL.md +186 -0
  141. package/skills/Security/Obfuscation/SKILL.md +226 -0
  142. package/skills/Security/Proguard/SKILL.md +202 -0
  143. package/skills/Security/R8/SKILL.md +234 -0
  144. package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
  145. package/skills/Security/Root Detection/SKILL.md +220 -0
  146. package/skills/Security/Secure Networking/SKILL.md +220 -0
  147. package/skills/System Integration/AlarmManager/SKILL.md +182 -0
  148. package/skills/System Integration/App Widget/SKILL.md +182 -0
  149. package/skills/System Integration/Deep Link/SKILL.md +187 -0
  150. package/skills/System Integration/Foreground Service/SKILL.md +212 -0
  151. package/skills/System Integration/Notification/SKILL.md +237 -0
  152. package/skills/System Integration/WorkManager/SKILL.md +256 -0
  153. package/skills/System Integration/clipboard/SKILL.md +155 -0
  154. package/skills/System Integration/share-intent/SKILL.md +182 -0
  155. package/skills/Testing/Compose Testing/SKILL.md +296 -0
  156. package/skills/Testing/Espresso/SKILL.md +292 -0
  157. package/skills/Testing/Fake Data/SKILL.md +245 -0
  158. package/skills/Testing/Integration Testing/SKILL.md +288 -0
  159. package/skills/Testing/Mocking/SKILL.md +229 -0
  160. package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
  161. package/skills/Testing/UI Testing/SKILL.md +293 -0
  162. package/skills/Testing/Unit Testing/SKILL.md +309 -0
  163. package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
  164. package/skills/UI System/Compose/SKILL.md +296 -0
  165. package/skills/UI System/Compose Animation/SKILL.md +281 -0
  166. package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
  167. package/skills/UI System/Compose Navigation/SKILL.md +255 -0
  168. package/skills/UI System/Compose Performance/SKILL.md +274 -0
  169. package/skills/UI System/Design System/SKILL.md +217 -0
  170. package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
  171. package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
  172. package/skills/UI System/Loading Strategy/SKILL.md +254 -0
  173. package/skills/UI System/Material 3/SKILL.md +279 -0
  174. package/skills/UI System/RTL/SKILL.md +179 -0
  175. package/src/index.ts +182 -0
  176. package/tsconfig.json +19 -0
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: espresso
3
+ description: >
4
+ Espresso UI testing for Android View-based UI.
5
+ Load this skill when writing instrumented tests for Views, Fragments,
6
+ or Activities, using ViewMatchers and ViewActions, testing RecyclerView,
7
+ or interacting with dialogs and menus.
8
+ ---
9
+
10
+ # Espresso
11
+
12
+ ## Overview
13
+ Espresso is Android's UI testing framework for View-based UI. It synchronizes automatically with the UI thread and AsyncTasks, making tests deterministic. Espresso operates through three components: `onView()` to find views, `ViewActions` to interact, and `ViewAssertions` to verify.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Espresso **auto-syncs** with the main thread — no `Thread.sleep()` needed
20
+ - Find views with **`ViewMatcher`** — prefer `withId()` and `withContentDescription()`
21
+ - **`IdlingResource`** for custom async operations that Espresso can't detect
22
+ - **Hilt** replaces real dependencies for isolated UI tests
23
+ - Espresso works alongside Compose tests in mixed View/Compose apps
24
+
25
+ ---
26
+
27
+ ## Dependencies
28
+
29
+ ```kotlin
30
+ // build.gradle.kts
31
+ dependencies {
32
+ androidTestImplementation(libs.espresso.core)
33
+ androidTestImplementation(libs.espresso.contrib) // RecyclerView, DrawerLayout
34
+ androidTestImplementation(libs.espresso.intents) // intent verification
35
+ androidTestImplementation(libs.androidx.test.rules)
36
+ androidTestImplementation(libs.androidx.test.runner)
37
+ }
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Basic Test Structure
43
+
44
+ ```kotlin
45
+ // ✅ Standard Espresso test
46
+ @RunWith(AndroidJUnit4::class)
47
+ @HiltAndroidTest
48
+ class UserListActivityTest {
49
+
50
+ @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
51
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(UserListActivity::class.java)
52
+
53
+ @Inject lateinit var fakeRepository: FakeUserRepository
54
+
55
+ @Before
56
+ fun setup() { hiltRule.inject() }
57
+
58
+ @Test
59
+ fun showsUserNameInList() {
60
+ fakeRepository.emit(listOf(
61
+ User(id = "1", name = "Ali Rezaei", email = Email("ali@test.com"))
62
+ ))
63
+
64
+ onView(withText("Ali Rezaei")).check(matches(isDisplayed()))
65
+ }
66
+
67
+ @Test
68
+ fun clickingUserOpensDetail() {
69
+ fakeRepository.emit(listOf(
70
+ User(id = "1", name = "Ali Rezaei", email = Email("ali@test.com"))
71
+ ))
72
+
73
+ onView(withText("Ali Rezaei")).perform(click())
74
+
75
+ onView(withId(R.id.user_detail_title)).check(matches(isDisplayed()))
76
+ }
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## ViewMatchers Reference
83
+
84
+ ```kotlin
85
+ // By ID
86
+ onView(withId(R.id.submit_button))
87
+
88
+ // By text
89
+ onView(withText("Submit"))
90
+ onView(withText(R.string.submit))
91
+ onView(withText(containsString("ubmit"))) // Hamcrest matcher
92
+
93
+ // By content description
94
+ onView(withContentDescription("Back"))
95
+ onView(withContentDescription(R.string.back))
96
+
97
+ // By hint
98
+ onView(withHint("Enter email"))
99
+
100
+ // By tag
101
+ onView(withTagKey(R.id.my_tag))
102
+ onView(withTagValue(equalTo("my_tag_value")))
103
+
104
+ // By type
105
+ onView(isAssignableFrom(EditText::class.java))
106
+
107
+ // Combining matchers
108
+ onView(allOf(withId(R.id.name_field), isDisplayed()))
109
+ onView(allOf(withText("Ali"), withParent(withId(R.id.user_card))))
110
+ ```
111
+
112
+ ---
113
+
114
+ ## ViewActions
115
+
116
+ ```kotlin
117
+ val view = onView(withId(R.id.my_view))
118
+
119
+ // Click
120
+ view.perform(click())
121
+ view.perform(longClick())
122
+ view.perform(doubleClick())
123
+
124
+ // Text input
125
+ view.perform(typeText("Hello"))
126
+ view.perform(clearText())
127
+ view.perform(replaceText("New text"))
128
+
129
+ // Keyboard
130
+ view.perform(pressImeActionButton())
131
+ view.perform(pressBack())
132
+ view.perform(closeSoftKeyboard())
133
+
134
+ // Scroll
135
+ view.perform(scrollTo())
136
+
137
+ // Swipe
138
+ view.perform(swipeLeft())
139
+ view.perform(swipeRight())
140
+ view.perform(swipeUp())
141
+ view.perform(swipeDown())
142
+ ```
143
+
144
+ ---
145
+
146
+ ## ViewAssertions
147
+
148
+ ```kotlin
149
+ val view = onView(withId(R.id.result_text))
150
+
151
+ // Visibility
152
+ view.check(matches(isDisplayed()))
153
+ view.check(matches(not(isDisplayed())))
154
+ view.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
155
+
156
+ // State
157
+ view.check(matches(isEnabled()))
158
+ view.check(matches(not(isEnabled())))
159
+ view.check(matches(isChecked()))
160
+ view.check(matches(isFocused()))
161
+
162
+ // Content
163
+ view.check(matches(withText("Expected")))
164
+ view.check(matches(withText(containsString("partial"))))
165
+ view.check(matches(withHint("placeholder")))
166
+
167
+ // Existence
168
+ view.check(matches(isDisplayed()))
169
+ view.check(doesNotExist())
170
+ ```
171
+
172
+ ---
173
+
174
+ ## RecyclerView
175
+
176
+ ```kotlin
177
+ // ✅ Scroll to position
178
+ onView(withId(R.id.recycler_view))
179
+ .perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(10))
180
+
181
+ // ✅ Click item at position
182
+ onView(withId(R.id.recycler_view))
183
+ .perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
184
+
185
+ // ✅ Click item matching a matcher
186
+ onView(withId(R.id.recycler_view))
187
+ .perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
188
+ hasDescendant(withText("Ali Rezaei")),
189
+ click()
190
+ ))
191
+
192
+ // ✅ Assert on item at position
193
+ onView(withId(R.id.recycler_view))
194
+ .check(RecyclerViewAssertions.withItemCount(5))
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Intent Verification (Espresso Intents)
200
+
201
+ ```kotlin
202
+ // ✅ Verify outgoing intents
203
+ @get:Rule
204
+ val intentsRule = IntentsRule()
205
+
206
+ @Test
207
+ fun clickShareOpensShareSheet() {
208
+ onView(withId(R.id.share_button)).perform(click())
209
+
210
+ intended(allOf(
211
+ hasAction(Intent.ACTION_SEND),
212
+ hasType("text/plain")
213
+ ))
214
+ }
215
+
216
+ // ✅ Stub incoming intents
217
+ intending(hasComponent(CameraActivity::class.java.name))
218
+ .respondWith(ActivityResult(Activity.RESULT_OK, Intent().apply {
219
+ putExtra("photo_uri", "content://mock_uri")
220
+ }))
221
+ ```
222
+
223
+ ---
224
+
225
+ ## IdlingResource for Custom Async
226
+
227
+ ```kotlin
228
+ // ✅ Register IdlingResource for operations Espresso can't detect
229
+ class NetworkIdlingResource : IdlingResource {
230
+ private var callback: IdlingResource.ResourceCallback? = null
231
+ private var isIdle = true
232
+
233
+ override fun getName() = "NetworkIdlingResource"
234
+ override fun isIdleNow() = isIdle
235
+ override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
236
+ this.callback = callback
237
+ }
238
+
239
+ fun setIdle(idle: Boolean) {
240
+ isIdle = idle
241
+ if (idle) callback?.onTransitionToIdle()
242
+ }
243
+ }
244
+
245
+ // Register in test
246
+ val idlingResource = NetworkIdlingResource()
247
+ IdlingRegistry.getInstance().register(idlingResource)
248
+
249
+ // Unregister in @After
250
+ IdlingRegistry.getInstance().unregister(idlingResource)
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Mixed Compose + View Testing
256
+
257
+ ```kotlin
258
+ // ✅ Espresso and Compose rules can coexist
259
+ @RunWith(AndroidJUnit4::class)
260
+ class MixedUiTest {
261
+
262
+ @get:Rule val activityRule = ActivityScenarioRule(MainActivity::class.java)
263
+ @get:Rule val composeRule = createAndroidComposeRule<MainActivity>()
264
+
265
+ @Test
266
+ fun testMixedUI() {
267
+ // Espresso for View
268
+ onView(withId(R.id.toolbar_title)).check(matches(withText("Home")))
269
+
270
+ // Compose for Compose content
271
+ composeRule.onNodeWithTag("compose_content").assertIsDisplayed()
272
+ }
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Anti-Patterns
279
+
280
+ - `Thread.sleep()` for synchronization — use `IdlingResource` or rely on Espresso auto-sync
281
+ - Testing with real network — fake the data layer via Hilt
282
+ - `onView(withIndex(...))` for RecyclerView items — use `RecyclerViewActions` instead
283
+ - Not calling `closeSoftKeyboard()` when keyboard may obscure views — causes view not found errors
284
+ - Sharing mutable test state between test methods — each test must be independent
285
+
286
+ ---
287
+
288
+ ## Related Skills
289
+ - `ui-testing` — broader UI test setup and Hilt configuration
290
+ - `compose-testing` — Compose-specific test APIs
291
+ - `integration-testing` — testing below the UI layer
292
+ - `hilt` — replacing dependencies in tests
@@ -0,0 +1,245 @@
1
+ ---
2
+ name: fake-data
3
+ description: >
4
+ Fake data and test factory patterns for Android tests.
5
+ Load this skill when building fake repositories, creating test data
6
+ with factory functions, designing object mothers, or maintaining
7
+ consistent test data across a test suite.
8
+ ---
9
+
10
+ # Fake Data
11
+
12
+ ## Overview
13
+ Fake data refers to two related concerns: fake implementations of interfaces (replacing real dependencies in tests) and test data factories (creating domain/entity objects with sensible defaults). Together they make tests readable, maintainable, and independent of production data.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - **Fakes are real implementations** — they implement the same interface, just in memory
20
+ - **Test data factories** provide defaults — only specify what's relevant to the test
21
+ - **Centralize test data** — one place to update when models change
22
+ - **Named factory variants** for common test scenarios (e.g., `adminUser()`, `suspendedUser()`)
23
+ - **Deterministic** — fake data never uses random values unless explicitly testing randomness
24
+
25
+ ---
26
+
27
+ ## Fake Repository
28
+
29
+ ```kotlin
30
+ // ✅ Fake repository — in-memory implementation of the real interface
31
+ class FakeUserRepository @Inject constructor() : UserRepository {
32
+
33
+ private val _users = MutableStateFlow<List<User>>(emptyList())
34
+ var shouldFail = false
35
+ var failureMessage = "Fake failure"
36
+
37
+ // ── Reactive ───────────────────────────────────────────────
38
+ override fun observeUsers(): Flow<List<User>> = _users.asStateFlow()
39
+
40
+ override fun observeUser(id: String): Flow<User?> =
41
+ _users.map { users -> users.find { it.id == id } }
42
+
43
+ // ── Suspend ────────────────────────────────────────────────
44
+ override suspend fun getUser(id: String): Result<User> {
45
+ if (shouldFail) return Result.failure(Exception(failureMessage))
46
+ return _users.value.find { it.id == id }
47
+ ?.let { Result.success(it) }
48
+ ?: Result.failure(Exception("User not found: $id"))
49
+ }
50
+
51
+ override suspend fun getUsers(): Result<List<User>> {
52
+ if (shouldFail) return Result.failure(Exception(failureMessage))
53
+ return Result.success(_users.value)
54
+ }
55
+
56
+ override suspend fun createUser(user: User): Result<User> {
57
+ if (shouldFail) return Result.failure(Exception(failureMessage))
58
+ _users.update { it + user }
59
+ return Result.success(user)
60
+ }
61
+
62
+ override suspend fun updateUser(user: User): Result<Unit> {
63
+ if (shouldFail) return Result.failure(Exception(failureMessage))
64
+ _users.update { users -> users.map { if (it.id == user.id) user else it } }
65
+ return Result.success(Unit)
66
+ }
67
+
68
+ override suspend fun deleteUser(id: String): Result<Unit> {
69
+ if (shouldFail) return Result.failure(Exception(failureMessage))
70
+ _users.update { users -> users.filter { it.id != id } }
71
+ return Result.success(Unit)
72
+ }
73
+
74
+ // ── Test helpers ───────────────────────────────────────────
75
+ fun emit(users: List<User>) { _users.value = users }
76
+ fun emitOne(user: User) { _users.value = listOf(user) }
77
+ fun clear() { _users.value = emptyList() }
78
+ fun addUser(user: User) { _users.update { it + user } }
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Domain Model Factory
85
+
86
+ ```kotlin
87
+ // ✅ Factory object with defaults — only override what matters
88
+ object UserFactory {
89
+
90
+ fun create(
91
+ id: String = "user-1",
92
+ name: String = "Ali Rezaei",
93
+ email: String = "ali@test.com",
94
+ role: UserRole = UserRole.MEMBER,
95
+ status: UserStatus = UserStatus.ACTIVE,
96
+ createdAt: Instant = Instant.parse("2024-01-01T00:00:00Z")
97
+ ): User = User(
98
+ id = id,
99
+ name = name,
100
+ email = Email(email),
101
+ role = role,
102
+ status = status,
103
+ createdAt = createdAt
104
+ )
105
+
106
+ // ✅ Named variants for common scenarios
107
+ fun admin() = create(id = "admin-1", name = "Admin User", role = UserRole.ADMIN)
108
+ fun guest() = create(id = "guest-1", name = "Guest User", role = UserRole.GUEST)
109
+ fun suspended() = create(id = "suspended-1", status = UserStatus.SUSPENDED)
110
+
111
+ // ✅ List factory
112
+ fun list(count: Int = 3): List<User> = (1..count).map {
113
+ create(id = "user-$it", name = "User $it", email = "user$it@test.com")
114
+ }
115
+ }
116
+
117
+ // ✅ Usage in tests — only specify what matters
118
+ val user = UserFactory.create(name = "Custom Name")
119
+ val admin = UserFactory.admin()
120
+ val users = UserFactory.list(5)
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Entity Factory (for Room Tests)
126
+
127
+ ```kotlin
128
+ // ✅ Separate factory for DB entities
129
+ object UserEntityFactory {
130
+
131
+ fun create(
132
+ id: String = "user-1",
133
+ name: String = "Ali Rezaei",
134
+ email: String = "ali@test.com",
135
+ role: String = "member",
136
+ isActive: Boolean = true,
137
+ createdAt: Long = 1704067200000L,
138
+ updatedAt: Long = 1704067200000L
139
+ ): UserEntity = UserEntity(
140
+ id = id, name = name, email = email,
141
+ role = role, isActive = isActive,
142
+ createdAt = createdAt, updatedAt = updatedAt
143
+ )
144
+
145
+ fun list(count: Int = 3): List<UserEntity> = (1..count).map {
146
+ create(id = "user-$it", name = "User $it", email = "user$it@test.com")
147
+ }
148
+ }
149
+ ```
150
+
151
+ ---
152
+
153
+ ## DTO Factory (for API Tests)
154
+
155
+ ```kotlin
156
+ // ✅ Factory for API response DTOs
157
+ object UserDtoFactory {
158
+
159
+ fun create(
160
+ id: String = "user-1",
161
+ name: String = "Ali Rezaei",
162
+ email: String = "ali@test.com",
163
+ role: String = "member"
164
+ ): UserDto = UserDto(id = id, name = name, email = email, role = role)
165
+
166
+ fun list(count: Int = 3): List<UserDto> = (1..count).map {
167
+ create(id = "user-$it", name = "User $it", email = "user$it@test.com")
168
+ }
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## UiState Factory
175
+
176
+ ```kotlin
177
+ // ✅ Factories for UI state — useful for Compose Preview and tests
178
+ object UserListUiStateFactory {
179
+
180
+ fun loading() = UserListUiState.Loading
181
+
182
+ fun success(
183
+ users: List<User> = UserFactory.list()
184
+ ) = UserListUiState.Success(users)
185
+
186
+ fun error(
187
+ message: String = "Something went wrong"
188
+ ) = UserListUiState.Error(message)
189
+
190
+ fun empty() = UserListUiState.Success(emptyList())
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Fake Dispatcher
197
+
198
+ ```kotlin
199
+ // ✅ Fake dispatcher for controlling coroutine execution in tests
200
+ class FakeDispatcherProvider : DispatcherProvider {
201
+ override val main: CoroutineDispatcher = UnconfinedTestDispatcher()
202
+ override val io: CoroutineDispatcher = UnconfinedTestDispatcher()
203
+ override val default: CoroutineDispatcher = UnconfinedTestDispatcher()
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Test Fixtures in androidTest
210
+
211
+ ```kotlin
212
+ // ✅ Shared test fixtures in :core:testing module
213
+ // core/testing/src/main/kotlin/com/example/testing/
214
+
215
+ // Factories
216
+ UserFactory.kt
217
+ OrderFactory.kt
218
+ ProductFactory.kt
219
+
220
+ // Fakes
221
+ FakeUserRepository.kt
222
+ FakeOrderRepository.kt
223
+ FakeNetworkMonitor.kt
224
+
225
+ // Rules
226
+ MainDispatcherRule.kt
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Anti-Patterns
232
+
233
+ - Hardcoding IDs like `"1"` scattered across all tests — use factory defaults
234
+ - Creating full objects in every test — use factories with defaults, override only what matters
235
+ - Mutable shared state in fakes — each test should reset; use `@BeforeEach` or `clear()`
236
+ - Fake that diverges from real implementation — a fake that ignores method contracts is useless
237
+ - Using `Random.nextInt()` in factories — non-deterministic; use fixed values
238
+
239
+ ---
240
+
241
+ ## Related Skills
242
+ - `unit-testing` — factories used in unit tests
243
+ - `integration-testing` — fakes used in integration tests
244
+ - `mocking` — when to use fakes vs MockK mocks
245
+ - `domain-modeling` — domain models the factories create