From cc-mobile-android
Testing patterns used in this project — JUnit + MockK unit tests, `runTest` for coroutines, Turbine for Flow, Compose UI tests with `createComposeRule`, and Hilt-aware instrumentation tests. Load when writing, updating, or debugging tests of any kind.
How this skill is triggered — by the user, by Claude, or both
Slash command
/cc-mobile-android:android-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- `junit`, `kotlin-test`
junit, kotlin-testkotlinx-coroutines-testmockkturbineandroidx.compose.ui:ui-test-junit4 + ui-test-manifestandroidx.hilt:hilt-android-testing + hilt-compiler (ksp)androidx.arch.core:core-testing (for InstantTaskExecutorRule if any LiveData sneaks in)src/test/ # JVM unit tests — fast, no emulator
src/androidTest/ # Instrumented — emulator/device, Compose UI tests
class SubmitOrderUseCaseTest {
private val orders: OrderRepository = mockk()
private val clock: Clock = FakeClock("2026-04-22T00:00:00Z")
private val submit = SubmitOrderUseCase(orders, clock)
@Test
fun `returns Success when repository accepts the order`() = runTest {
val draft = OrderDraft(items = listOf(item()))
coEvery { orders.create(draft) } returns Result.success(ORDER)
val result = submit(draft)
assertThat(result.isSuccess).isTrue()
}
@Test
fun `returns Failure when repository returns error`() = runTest {
coEvery { orders.create(any()) } returns Result.failure(DomainError.Network)
val result = submit(OrderDraft(emptyList()))
assertThat(result.exceptionOrNull()).isEqualTo(DomainError.Network)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
class OrderViewModelTest {
private val dispatcher = StandardTestDispatcher()
private val submit: SubmitOrderUseCase = mockk()
private lateinit var viewModel: OrderViewModel
@Before
fun setUp() {
Dispatchers.setMain(dispatcher)
viewModel = OrderViewModel(submit)
}
@After fun tearDown() { Dispatchers.resetMain() }
@Test
fun `state transitions Loading -> Success`() = runTest(dispatcher) {
coEvery { submit(any()) } returns Result.success(ORDER)
viewModel.state.test {
assertThat(awaitItem()).isEqualTo(OrderUiState.Loading)
viewModel.onAction(OrderAction.Submit(DRAFT))
assertThat(awaitItem()).isInstanceOf(OrderUiState.Success::class.java)
}
}
}
Rules:
Dispatchers.Main.StandardTestDispatcher (not UnconfinedTestDispatcher) unless you have a specific reason.viewModel.state.value.flow.test {
assertThat(awaitItem()).isEqualTo(first)
assertThat(awaitItem()).isEqualTo(second)
awaitComplete()
}
cancelAndIgnoreRemainingEvents() when you don't care what happens after the assertion you care about.expectNoEvents() when asserting something did not emit.Default to fakes for classes with >2 methods or non-trivial behavior. Fakes are cheaper to maintain than 20 coEvery lines.
class FakeOrderRepository : OrderRepository {
var orders: Map<OrderId, Order> = emptyMap()
override suspend fun getOrder(id: OrderId) =
orders[id]?.let(Result.Companion::success)
?: Result.failure(DomainError.NotFound)
}
Mock only:
class OrderScreenTest {
@get:Rule val composeRule = createComposeRule()
@Test fun `shows order id in Success state`() {
composeRule.setContent {
AppTheme {
OrderScreen(
state = OrderUiState.Success(fakeOrder(id = "ABC")),
onBack = {},
onAction = {},
)
}
}
composeRule.onNodeWithText("Order ABC").assertIsDisplayed()
}
}
Prefer semantic matchers (onNodeWithText, onNodeWithContentDescription) over testTag. Only add a testTag when there's no natural semantic.
@HiltAndroidTest
class OrderFlowTest {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1) val composeRule = createAndroidComposeRule<MainActivity>()
@BindValue @JvmField
val repo: OrderRepository = FakeOrderRepository().apply { orders = mapOf(OrderId("x") to ORDER_X) }
@Before fun setUp() { hiltRule.inject() }
@Test fun `user can see seeded order`() { ... }
}
@BindValue replaces the real binding for this one test.
./gradlew :app:testDebugUnitTest --tests 'com.example.OrderViewModelTest'
./gradlew :app:connectedDebugAndroidTest --tests 'com.example.OrderFlowTest'
Thread.sleep or bare delay outside runTest.Room.inMemoryDatabaseBuilder only if you must).Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.
npx claudepluginhub dimitriremoiville/cc-mobile --plugin cc-mobile-android