npx claudepluginhub himattm/skills --plugin androidThis skill uses the workspace's default tool permissions.
The most common debugging failure is fixing the wrong thing. The agent sees a stack trace, theorizes a cause, edits a file, redeploys, and declares done — without evidence the change addresses the actual bug. A failing test changes that:
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Executes ctx7 CLI to fetch up-to-date library documentation, manage AI coding skills (install/search/generate/remove/suggest), and configure Context7 MCP. Useful for current API refs, skill handling, or agent setup.
Share bugs, ideas, or general feedback.
The most common debugging failure is fixing the wrong thing. The agent sees a stack trace, theorizes a cause, edits a file, redeploys, and declares done — without evidence the change addresses the actual bug. A failing test changes that:
verify-android-screen to confirmandroid-probe-logging or android-regression-diff-scan, then come back here# 1. JUnit version (4 vs 5 — Jupiter)
grep -rE 'junit:junit|junit-jupiter|junit5' app/build.gradle* gradle/libs.versions.toml 2>/dev/null
# 2. Mocking lib in use (don't introduce a second one)
grep -rE 'mockk|mockito|mockito-kotlin|mockito-core' app/build.gradle* gradle/libs.versions.toml 2>/dev/null
# 3. Robolectric configured?
grep -rE 'robolectric' app/build.gradle* gradle/libs.versions.toml 2>/dev/null
grep -A3 'unitTests' app/build.gradle*
# 4. Instrumentation runner
grep -E 'testInstrumentationRunner' app/build.gradle*
# 5. Module structure — multi-module projects need a different :module:test invocation
ls settings.gradle*
grep -E '^include' settings.gradle* 2>/dev/null | head
JUnit 4 vs JUnit 5. Most existing Android projects use JUnit 4 (org.junit.Test); newer setups may use Jupiter (org.junit.jupiter.api.Test). Match what's already there — mixing is possible but adds complexity. If unsure, look at an existing test in the project.
Mocking. Use whatever the project already uses:
| Project uses | Test setup |
|---|---|
MockK (io.mockk:mockk) | Pure Kotlin / coroutine-friendly. Default if both libs present. |
Mockito-Kotlin (org.mockito.kotlin:mockito-kotlin) | Kotlin-friendly Mockito wrappers; works fine with @Test. |
Mockito plain (org.mockito:mockito-core) | Java-style; works with Kotlin but verbose. |
| Nothing | Don't add a mocking lib for one test — use a hand-rolled fake or a direct constructor injection. |
Multi-module command shape. If your test class is in :feature:login, the gradle invocation is ./gradlew :feature:login:testDebugUnitTest --tests <FQN>, not :app:testDebugUnitTest. Run ./gradlew projects to see the module list.
Java codebase. All the test-layer tradeoffs are identical; the only thing that changes is the test source language. JUnit @Test works the same; Robolectric setup is identical. Mockito (plain) is the natural fit for Java tests.
Compose UI tests. If the bug is in a composable, the equivalent of "instrumentation" is androidx.compose.ui:ui-test-junit4, with createAndroidComposeRule. That's a separate setup from the regular instrumentation runner — grep ui-test-junit4 to confirm it's wired before writing one.
| Layer | When | Speed | Command |
|---|---|---|---|
| JVM unit (JUnit + MockK / Mockito) | Pure Kotlin/Java logic, ViewModels, repositories with mocked deps | Fast (sub-second per test) | ./gradlew :app:testDebugUnitTest --tests <FQN> |
| Robolectric | Code that uses Android framework classes (Context, Resources, SharedPreferences) but doesn't need a real device | Fast (~1s per test) | Same as JVM unit; Robolectric runs on the JVM target |
| Instrumentation | Real device behavior — UI, lifecycle, IPC, real database | Slow (10s+ per test, needs emulator) | ./gradlew :app:connectedDebugAndroidTest --tests <FQN> |
Default to the fastest layer that can express the bug. Pushing a JVM test up to instrumentation when a mock would do is a common time waster.
If your bug needs Android framework classes but the project has no Robolectric, ephemerally add it:
// app/build.gradle.kts — under android { ... }
testOptions {
unitTests {
isIncludeAndroidResources = true // Required for Robolectric
}
}
// app/build.gradle.kts — under dependencies { ... }
testImplementation("org.robolectric:robolectric:4.13")
Annotate the test class:
@RunWith(RobolectricTestRunner::class)
@Config(application = Application::class, sdk = [33])
class LoginViewModelTest { /* ... */ }
Use @Config(application = MyApp::class) if your repro needs your real Application subclass; pass Application::class (the framework default) when you don't. Robolectric defaults sdk to the project's targetSdk; pin it explicitly when the bug is API-level-sensitive. Keep the dependency add scoped to this PR if the rest of the project doesn't use Robolectric — or coordinate adding it as a permanent dev dependency separately.
// Bad
@Test fun bug_1234()
// Bad
@Test fun crashOnLogin()
// Good — names the actual broken behavior
@Test fun loginRetainsEmailAfterRotation()
@Test fun fetchUserReturnsCachedValueWhenOffline()
@Test fun submitButtonDisabledUntilEmailValid()
The test name is the bug spec. Future readers should understand the contract from the name alone.
The test must fail because of the bug, not because of a typo or missing setup. Read the failure output — the assertion message should describe the bug, not "NullPointerException at MyTest.kt:42."
A common antipattern: writing a passing test, then "fixing" the bug, then claiming success. If the test never failed, it never proved anything.
Run only your new test on each iteration — the full suite is wasted time during a fix:
./gradlew :app:testDebugUnitTest --tests com.example.user.LoginViewModelTest.loginRetainsEmailAfterRotation
For instrumentation:
./gradlew :app:connectedDebugAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.example.user.LoginFlowTest#emailRetainedAcrossRotation
./gradlew :app:testDebugUnitTest and/or connectedDebugAndroidTest) to confirm no regression elsewhere.The new test stays — it's the regression guard. But before declaring done:
git status — no scratch files (/tmp/, commented-out test bodies, @Ignored siblings).rg '@Ignore|TODO\(.*test' — no disabled tests left behind.@Ignore instead of fix. Never disable a failing test. Fix the bug or the test.Most of this skill is single-threaded — write, run, read short failure output. Delegate only when:
app/build/reports/tests/testDebugUnitTest/index.html (or the equivalent XML in app/build/test-results/) and report N passed, M failed plus the first failure's class + method + message. Under 60 words.| Mistake | Fix |
|---|---|
| Writing the fix first, then the test | Test FIRST. If you can't write a failing test, you don't understand the bug. Stop and investigate. |
| Test passes on the broken code | Re-read the failure (or absence of one) — the test isn't actually exercising the bug. Refine until it goes red. |
| Reaching for instrumentation when a JVM unit test would do | Push the bug down to the fastest layer that can express it; instrumentation tests are 10–100× slower |
| Running the whole suite each iteration | Use --tests <FQN> to target the one test until it passes; full suite once at the end |
Test name = bug_1234 or testFoo | Name it for the contract: loginRetainsEmailAfterRotation |
@Ignoreing the failing test to "come back later" | Never disable a test instead of fixing it; that's how regressions ship |
Asserting on verify(mock).callMethod(any()) | Assert observable behavior, not call counts; behavior survives refactor, mocks don't |
| Skipping the full-suite run after green | Your fix may have broken something else; the focused-then-full sequence is the contract |