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.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- 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
|