Testing Skill
TypeCellOS/BlockNote
Instructions for writing, running, and updating unit/end-to-end tests.
A skill your agent uses to run Android-aware unit tests on the JVM with Robolectric — the right runner choice (AndroidJUnit4 vs RobolectricTestRunner), @Config sdk/qualifiers/application, the…
$ npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install skydoves/android-testing-skills using-robolectric-correctly --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/jvm-tests/robolectric/using-robolectric-correctly .claude/skills/using-robolectric-correctly && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "using-robolectric-correctly" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/jvm-tests/robolectric/using-robolectric-correctly into .claude/skills/using-robolectric-correctly/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "using-robolectric-correctly", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/skydoves/android-testing-skills/tree/main/jvm-tests/robolectric/using-robolectric-correctlyType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install skydoves/android-testing-skills using-robolectric-correctly --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/jvm-tests/robolectric/using-robolectric-correctly .agents/skills/using-robolectric-correctly && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "using-robolectric-correctly" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/jvm-tests/robolectric/using-robolectric-correctly into .agents/skills/using-robolectric-correctly/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "using-robolectric-correctly", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install skydoves/android-testing-skills using-robolectric-correctly --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/jvm-tests/robolectric/using-robolectric-correctly .cursor/skills/using-robolectric-correctly && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "using-robolectric-correctly" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/jvm-tests/robolectric/using-robolectric-correctly into .cursor/skills/using-robolectric-correctly/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "using-robolectric-correctly", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/skydoves/android-testing-skills.git --path jvm-tests/robolectric/using-robolectric-correctly--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install skydoves/android-testing-skills using-robolectric-correctly --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/jvm-tests/robolectric/using-robolectric-correctly .gemini/skills/using-robolectric-correctly && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "using-robolectric-correctly" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/jvm-tests/robolectric/using-robolectric-correctly into .gemini/skills/using-robolectric-correctly/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "using-robolectric-correctly", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install skydoves/android-testing-skills using-robolectric-correctlyInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/jvm-tests/robolectric/using-robolectric-correctly .github/skills/using-robolectric-correctly && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "using-robolectric-correctly" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/jvm-tests/robolectric/using-robolectric-correctly into .github/skills/using-robolectric-correctly/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "using-robolectric-correctly", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install skydoves/android-testing-skills using-robolectric-correctly --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/jvm-tests/robolectric/using-robolectric-correctly .opencode/skills/using-robolectric-correctly && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "using-robolectric-correctly" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/jvm-tests/robolectric/using-robolectric-correctly into .opencode/skills/using-robolectric-correctly/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "using-robolectric-correctly", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
using-robolectric-correctlyA skill your agent uses to run Android-aware unit tests on the JVM with Robolectric — the right runner choice (AndroidJUnit4 vs RobolectricTestRunner), @Config sdk/qualifiers/application, the…
Using Robolectric Correctly is an agent skill from skydoves/android-testing-skills. Use this skill to run Android-aware unit tests on the JVM with Robolectric — the right runner choice (AndroidJUnit4 vs RobolectricTestRunner), @Config sdk/qualifiers/application, the includeAndroidResources requirement, common shadows (ShadowApplication, ShadowLog, ShadowLooper, ShadowPackageManager, ShadowSystemClock), looper draining (shadowOf(Looper.getMainLooper()).idle()), and when NOT to reach for Robolectric (screenshot tests, RenderThread, accessibility services). Also covers the AGP 7.2+ sharedTest…
Its SKILL.md is about 5.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Testing & QA, covering Unit testing and Accessibility. It works with Android. The repository describes itself as: ⚡️ A set of skills for Android testing: Compose UI, AndroidX Test, JVM unit tests, and ADB. The licence is Apache-2.0.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 8665ed5. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are kotlin and gradle).
From the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
robolectric.orgdeveloper.android.comgithub.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Using Robolectric Correctly loads about 5.4k tokens when it runs. Until then it costs about 232 tokens; SKILL.md has 1,391 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from skydoves/android-testing-skills at commit 8665ed5, republished under its Apache-2.0 licence (© skydoves). 1,391 words, ~5,405 tokens.
.claude/skills/using-robolectric-correctly/SKILL.md (or your agent's skills folder).Robolectric simulates Android on the JVM by swapping in pure-Java reimplementations of system classes ("shadows"). It is fast (10-100x vs an emulator) but not an emulator: no real RenderThread, no real Binder, no real GPU. This skill locks down the runner choice, the @Config matrix, the looper draining ritual, and the AGP 7.2+ sharedTest gotcha. Compose-on-Robolectric specifics live in ../../../compose/synchronization/controlling-the-test-clock/SKILL.md.
android.* (Context, Resources, View, PackageManager, Looper) and the developer wants to test it on the JVM in seconds, not on an emulator in minutes.RuntimeException: Method ... not mocked (the bare-Android-jar default) — the developer needs Robolectric's shadows.Resources$NotFoundException — testOptions.unitTests.includeAndroidResources is missing.Handler.postDelayed / Looper queue does not advance under Robolectric's default LooperMode.PAUSED.sharedTest / androidHostTest+androidDeviceTest).sharedTest srcDir sharing pattern.android.* imports. Robolectric pays a 1-3 s class-load tax for nothing — use plain JUnit4. See ../../runner/configuring-junit4-on-android/SKILL.md.RenderThread timing. Run on an emulator/device. See ../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md.runTest and does not need framework Android. Use ../../coroutines/testing-coroutines-with-runtest/SKILL.md.../../../compose/setup/configuring-test-dependencies/SKILL.md and the clock semantics live in ../../../compose/synchronization/controlling-the-test-clock/SKILL.md.testImplementation("junit:junit:4.13.2")
testImplementation("org.robolectric:robolectric:4.x")
testImplementation("androidx.test:core:1.7.0") // ApplicationProvider works on both runtimes
testImplementation("androidx.test.ext:junit:1.3.0") // androidx.test.ext.junit.runners.AndroidJUnit4(docs/CORPUS.md §G.1; R7.)
android {
testOptions {
unitTests {
includeAndroidResources = true // non-negotiable
}
}
}includeAndroidResources is required if any test (or any class loaded by any test) touches R.*, Resources, layouts, themes, or anything from res/. Without it, resource lookups throw at test startup (R7).
Robolectric 4.x supports SDKs 21 through the latest stable. The androidx Compose host tests pin a floor of 23 — quote it as a single source of truth:
// androidx/compose/ui/ui-test/src/androidHostTest/.../Constants.kt
internal const val RobolectricMinSdk = 23(R7.)
Two runners work; they read @Config identically.
| Runner | When |
|---|---|
androidx.test.ext.junit.runners.AndroidJUnit4 | Default. A router: delegates to Robolectric on JVM and to the real AndroidJUnit4ClassRunner on a device. Linchpin of the sharedTest pattern. |
org.robolectric.RobolectricTestRunner | Only when the test must never run on a device — e.g. uses Robolectric-specific shadow APIs like shadowOf(Looper.getMainLooper()).idle() directly, or needs a Robolectric subclass like ParameterizedRobolectricTestRunner. |
(R7; docs/CORPUS.md §G.6.)
import androidx.test.ext.junit.runners.AndroidJUnit4
@RunWith(AndroidJUnit4::class)
@Config(minSdk = 23)
class FooTest { /* runs under Robolectric on JVM, real runner on device */ }
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [33])
class LooperBoundTest { /* uses ShadowLooper APIs directly — JVM only */ }CRITICAL: androidx.test.runner.AndroidJUnit4 (the runner package, not ext.junit.runners) is @Deprecated (docs/CORPUS.md §G.2). Always import from androidx.test.ext.junit.runners.
@Config configures the simulated Android environment per class or per method. Method-level overrides class-level; subclasses inherit and may override.
// Single SDK
@Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE])
class FooTest
// SDK matrix — runs once per listed SDK
@Config(sdk = [21, 28, 33])
class BarTest
// Range — runs once at the project's targetSdk if it is >= minSdk
@Config(minSdk = 23)
class BazTest
// Per-method override
class QuxTest {
@Test @Config(sdk = [Build.VERSION_CODES.TIRAMISU])
fun onTiramisuOnly() { /* ... */ }
@Test @Config(qualifiers = "fr-rFR-w820dp") // locale + screen
fun frenchLayout() { /* ... */ }
@Test @Config(qualifiers = "+night") // additive: keep, force night
fun darkColors() { /* ... */ }
}
// Override the Application instance
@Config(application = MyTestApplication::class)
class WithFakeApp(R7.)
Constraint: sdk and minSdk/maxSdk cannot coexist on the same annotation. minSdk and maxSdk may be combined.
CI-time hot take: prefer @Config(sdk = [<one SDK>]) on the commit gate; reserve multi-SDK matrices for the merge gate. Each SDK in the matrix re-runs the whole class.
Shadows are JVM-friendly fakes for Android system classes. Robolectric ships hundreds; you can write your own with @Implements/@Implementation. Common ones:
| Shadow | Purpose |
|---|---|
ShadowApplication | Inspect started services / broadcasts / granted permissions. |
ShadowLog | Capture android.util.Log output (ShadowLog.stream = System.out). |
ShadowSystemClock | Advance / freeze SystemClock.elapsedRealtime() / uptimeMillis(). |
ShadowLooper | Drive message queues; idleMainLooper(), runUiThreadTasksIncludingDelayedTasks(). |
ShadowPackageManager | Add/remove packages, set system features, queryable intents. |
ShadowAlarmManager | Inspect scheduled alarms without firing them. |
ShadowNotificationManager | Inspect posted notifications and channels. |
ShadowInputManager | Add/remove InputDevices for input tests. |
ShadowContentResolver | Register fake providers, observe inserts/queries. |
Shadows.shadowOf(realObject) returns the shadow instance. Static-import and rely on overload resolution:
import androidx.test.core.app.ApplicationProvider
import org.robolectric.Shadows.shadowOf
import org.robolectric.shadows.ShadowPackageManager
val app: Application = ApplicationProvider.getApplicationContext()
val shadowPm: ShadowPackageManager = shadowOf(app.packageManager)
shadowPm.setSystemFeature(PackageManager.FEATURE_CAMERA_ANY, true)Robolectric's main looper is paused in the default LooperMode.PAUSED runtime mode. Handler messages posted by the SUT do NOT run automatically. Drain explicitly:
import android.os.Looper
import org.robolectric.Shadows.shadowOf
import org.robolectric.shadows.ShadowLooper
import java.time.Duration
// Drain everything currently queued on the main looper.
shadowOf(Looper.getMainLooper()).idle()
// Run delayed tasks too (postDelayed et al).
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
// Advance virtual time by N ms; only handlers due before that time fire.
shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(500))(R7.)
This complements (and is independent from) Compose's MainTestClock.advanceTimeBy(...). The Compose clock and the platform looper are separate. For Compose-on-Robolectric, runComposeUiTest { … waitForIdle() } drives the looper for you via RobolectricIdlingStrategy — outside Compose, drain manually.
The classic sharedTest pattern (per the 2021 Robolectric blog post) puts one source directory under both test and androidTest:
// LEGACY — broken on AGP 7.2+
android {
sourceSets {
String dir = 'src/sharedTest/'
test { java.srcDirs += dir + 'java' }
androidTest { java.srcDirs += dir + 'java' }
}
}CRITICAL: starting with AGP 7.2+, you cannot put the same source directory into multiple source sets (Robolectric GitHub issue #7432; R7). The build fails with "Source directory ... already added to source set".
Modern alternatives (PREFERRED order):
commonTest -> {androidHostTest, androidDeviceTest} via the androidXMultiplatform plugin. Compose internally migrated to this:sourceSets {
androidCommonTest { dependsOn(commonTest) }
androidDeviceTest { dependsOn(androidCommonTest) } // instrumented APK
androidHostTest { dependsOn(androidCommonTest) } // Robolectric on JVM
}test and androidTest depend on as testImplementation / androidTestImplementation.Both androidHostTest and androidDeviceTest can use @RunWith(AndroidJUnit4::class) unchanged because AndroidJUnit4 is a router. Source-set wiring is ../../../fundamentals/strategies/organizing-test-source-sets/SKILL.md.
| Scenario | Where it belongs |
|---|---|
Pure Kotlin, no android.* imports | Plain JUnit4 in src/test/ (no Robolectric). |
| Pixel-correct rendering, screenshot tests | Emulator/device — RenderThread is real there, not on Robolectric. |
| Accessibility service behavior | Emulator/device. Compose's enableAccessibilityChecks installs the validator on Robolectric AND logs a warning (Build.FINGERPRINT.lowercase() == "robolectric"); Robolectric does not faithfully drive the accessibility services so any pass is inconclusive (b/332778271). |
Choreographer fidelity, animation timing close to production | Emulator/device. |
| GPU/Skia behavior | Emulator/device. @GraphicsMode(GraphicsMode.Mode.NATIVE) opt-in is a stub of the real pipeline. |
// WRONG
@RunWith(RobolectricTestRunner::class)
@Config(minSdk = 23)
class LoginViewModelTest {
@Test fun loginFlow() { /* ... */ }
}
// WRONG because: this class CAN run unchanged on a device too — there are no
// Robolectric-only APIs in the body. Pinning to RobolectricTestRunner locks the
// test to the JVM and forfeits the sharedTest pattern. The same class moved to
// src/androidTest/ (or androidDeviceTest) would not run.// RIGHT
@RunWith(AndroidJUnit4::class)
@Config(minSdk = RobolectricMinSdk) // pin via single source of truth
class LoginViewModelTest {
@Test fun loginFlow() { /* ... */ }
}// WRONG
@RunWith(AndroidJUnit4::class)
class HandlerTest {
@Test fun postedRuns() {
var fired = false
Handler(Looper.getMainLooper()).post { fired = true }
assertTrue(fired) // FAIL: runnable is queued, not run
}
}
// WRONG because: Robolectric's default LooperMode.PAUSED does not auto-run posted
// runnables. The post is queued; assertTrue runs before the queue is drained.// RIGHT
@RunWith(AndroidJUnit4::class)
class HandlerTest {
@Test fun postedRuns() {
var fired = false
Handler(Looper.getMainLooper()).post { fired = true }
shadowOf(Looper.getMainLooper()).idle() // drain queue
assertTrue(fired)
}
}// WRONG
android {
testOptions {
unitTests { /* includeAndroidResources missing */ }
}
}Symptom: android.content.res.Resources$NotFoundException: String resource ID #0x7f0c0001 at test startup. The fix:
// RIGHT
android {
testOptions {
unitTests {
includeAndroidResources = true // Groovy
// isIncludeAndroidResources = true // Kotlin DSL
}
}
}// WRONG
android {
sourceSets {
test { java.srcDirs += 'src/sharedTest/java' }
androidTest { java.srcDirs += 'src/sharedTest/java' }
}
}
// WRONG because: AGP 7.2+ rejects sharing a single source directory across
// source sets (Robolectric issue #7432). Build fails with "Source directory
// already added to source set".// RIGHT — AndroidX-style KMP source sets
sourceSets {
androidCommonTest { dependsOn(commonTest) }
androidDeviceTest { dependsOn(androidCommonTest) }
androidHostTest { dependsOn(androidCommonTest) }
}// androidx/compose/ui/ui-test/.../ViewVisibilityRobolectricTest.kt (paraphrased)
@RunWith(ParameterizedRobolectricTestRunner::class)
@Config(minSdk = RobolectricMinSdk)
class ViewVisibilityRobolectricTest(private val visibility: Int) {
companion object {
@JvmStatic
@Parameters(name = "visibility={0}")
fun params() = listOf(View.VISIBLE, View.INVISIBLE, View.GONE)
}
@Test fun behaves() { /* uses `visibility` */ }
}This is the one place to pin RobolectricTestRunner (its parameterized subclass) — there is no AndroidJUnit4 parameterized variant.
// androidx/compose/ui/ui-test/.../RobolectricIdlingStrategy.android.kt
internal val HasRobolectricFingerprint
get() = Build.FINGERPRINT.lowercase() == "robolectric"This idiom is widespread inside androidx (Compose accessibility, idling, graphics layers). Library code under test SHOULD avoid copying it unless the branch is genuinely test-only — misdetection silently changes behavior.
AndroidJUnit4 from androidx.test.ext.junit.runners. MUST NOT import from androidx.test.runner — that one is @Deprecated (docs/CORPUS.md §G.2).@RunWith(AndroidJUnit4::class) over @RunWith(RobolectricTestRunner::class). The latter only when the test uses Robolectric-specific APIs (e.g. shadowOf(Looper.getMainLooper()).idle()) directly, or needs ParameterizedRobolectricTestRunner.testOptions.unitTests.includeAndroidResources = true for any project whose unit tests touch R.* / Resources / layouts / themes.shadowOf(Looper.getMainLooper()).idle() (or ShadowLooper.runUiThreadTasksIncludingDelayedTasks()) before asserting anything that depends on a Handler.post* callback. Default LooperMode.PAUSED does NOT auto-run.@Config(minSdk = …) (or sdk = […]) — do not depend on the project's default SDK varying in CI.srcDirs sharing pattern test.java.srcDirs += 'src/sharedTest/java' on AGP 7.2+. Migrate to androidHostTest + androidDeviceTest (KMP source sets) or a separate Gradle module.android.* imports. The class-load tax is 1-3 s for nothing.RenderThread timing — use an emulator/device.RobolectricMinSdk = 23 from androidx/compose/ui/ui-test/src/androidHostTest/.../Constants.kt as the single source of truth for the floor SDK in androidHostTest modules.@RunWith(RobolectricTestRunner::class) across many small classes.@Config(sdk = [33]) on the commit gate; multi-SDK matrices on the merge gate.androidx.test.runner.AndroidJUnit4 (use androidx.test.ext.junit.runners.AndroidJUnit4). grep -rn 'androidx.test.runner.AndroidJUnit4' src/test src/androidTest returns empty.testOptions.unitTests.includeAndroidResources = true is set in the module's build.gradle(.kts).@RunWith(RobolectricTestRunner::class) exists on a test class that does not actually use Robolectric-specific APIs.Handler / triggers Looper work calls shadowOf(Looper.getMainLooper()).idle() (or runUiThreadTasksIncludingDelayedTasks) before asserting.@Config(minSdk = …) or @Config(sdk = […]).androidHostTest + androidDeviceTest (or a shared module), not legacy srcDirs += sharedTest.src/androidTest/ or androidDeviceTest../gradlew :module:test passes; ./gradlew :module:connectedDebugAndroidTest passes for any test in androidCommonTest.tasks/research/R7-robolectric.md.docs/CORPUS.md §G.1 / §G.6 — Gradle coordinates and runner choice.androidx/compose/ui/ui-test/src/androidHostTest/.../Constants.kt — RobolectricMinSdk = 23 floor.androidx/compose/ui/ui-test/.../RobolectricComposeTest.kt — canonical @RunWith(AndroidJUnit4::class) @Config(minSdk = RobolectricMinSdk) cross-runtime test.androidx/compose/ui/ui-test/.../PrefetchNotHangingMainThreadTest.kt — @RunWith(RobolectricTestRunner::class) with explicit shadowOf(Looper.getMainLooper()).idle().androidx/compose/ui/ui-test/.../ViewVisibilityRobolectricTest.kt — ParameterizedRobolectricTestRunner example.androidx/compose/ui/ui-test/.../RobolectricIdlingStrategy.android.kt — Build.FINGERPRINT.lowercase() == "robolectric" detection idiom.androidx/compose/ui/ui/.../MediaQueryIntegrationTest.kt — ShadowPackageManager / ShadowInputManager in an @RunWith(AndroidJUnit4::class) test.../../coroutines/testing-coroutines-with-runtest/SKILL.md — runTest + MainDispatcherRule (independent of Robolectric).../../coroutines/testing-flows-with-turbine/SKILL.md — Flow assertions; Turbine works under Robolectric unchanged.../../runner/configuring-junit4-on-android/SKILL.md — JUnit4 plumbing surrounding Robolectric.../../mocking/mocking-with-mockito/SKILL.md — mocking Android types under Robolectric.../../mocking/mocking-with-mockk/SKILL.md — mockkStatic(Build::class) for fingerprint branches.../../../fundamentals/strategies/applying-testing-strategies/SKILL.md — Robolectric is "medium" tests in Google's three-size framing.../../../fundamentals/strategies/organizing-test-source-sets/SKILL.md — src/test/ vs src/androidTest/ vs androidHostTest/androidDeviceTest.../../../compose/synchronization/controlling-the-test-clock/SKILL.md — Compose's MainTestClock is separate from ShadowLooper.../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md — when the same test source needs to run on a device.© skydoves, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in jvm-tests/robolectric/using-robolectric-correctly of skydoves/android-testing-skills.
Open the folder on GitHubat commit 8665ed5
Using Robolectric Correctly next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Using Robolectric Correctly this skillskydoves/android-testing-skills | 333 | — | ~5.4k | Automated safety check: Pass | Apache-2.0 | |
| Testing SkillTypeCellOS/BlockNote | 10k | — | ~2.6k | Automated safety check: Pass | Custom licence | |
| Mesh Labpermissionlesstech/bitchat-android | 7.8k | — | ~2.9k | Automated safety check: Pass | GPL-3.0 | |
| Playwright Testingchongdashu/vibejam-starter-pack | 149 | — | ~2.1k | Automated safety check: Pass | None | |
| Playwright Testingchongdashu/vibejam-starter-pack | 149 | — | ~2.2k | Automated safety check: Pass | None | |
| Ha Android Testinghome-assistant/android | 4k | — | ~2k | Automated safety check: Pass | Apache-2.0 |
TypeCellOS/BlockNote
Instructions for writing, running, and updating unit/end-to-end tests.
permissionlesstech/bitchat-android
Run, diagnose, and extend bitchat Android Mesh Lab physical-device tests.
chongdashu/vibejam-starter-pack
Plan, implement, and debug frontend tests: unit/integration/E2E/visual/a11y.
chongdashu/vibejam-starter-pack
Plan, implement, and debug frontend tests: unit/integration/E2E/visual/a11y.
home-assistant/android
Home Assistant Android testing guidance. An agent skill from home-assistant/android.
osama-raddad/FireCrasher
Add or update a Robolectric JVM unit test for the FireCrasher library.
skydoves/android-testing-skills
A skill your agent uses to verify Compose layout measurements from a UI test using assertWidthIsEqualTo, assertHeightIsEqualTo, assertWidthIsAtLeast, assertHeightIsAtLeast…
skydoves/android-testing-skills
A skill your agent uses to verify a Compose semantics node's properties from a UI test using assertExists, assertDoesNotExist, assertIsDisplayed, assertIsNotDisplayed, assertIsDeactivated…
skydoves/android-testing-skills
A skill your agent uses to render every Jetpack Compose @Preview as a screenshot on a real Android device or emulator and publish a browsable HTML catalog from CI.
skydoves/android-testing-skills
A skill your agent uses to capture visual artefacts from a device for test failures, golden image generation, QA repro, and demo videos.
skydoves/android-testing-skills
A skill your agent uses to pick the correct Compose UI test entry point.
skydoves/android-testing-skills
A skill your agent uses to pick which behaviors to cover in an Android test suite using Google's five-category state vocabulary plus the explicit "what NOT to test" list from…
Works with
Categories
A skill your agent uses to run Android-aware unit tests on the JVM with Robolectric — the right runner choice (AndroidJUnit4 vs RobolectricTestRunner), @Config sdk/qualifiers/application, the…. Using Robolectric Correctly is an agent skill from skydoves/android-testing-skills.idle()), and when NOT to reach for Robolectric (screenshot tests, RenderThread, accessibility services).
Using Robolectric Correctly fits situations like: run Android-aware unit tests on the JVM with Robolectric — the right runner choice (AndroidJUnit4 vs RobolectricTestRunner); @Config sdk/qualifiers/application; the includeAndroidResources requirement; common shadows (ShadowApplication.
Run `npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a claude-code`. Or copy the skill folder (jvm-tests/robolectric/using-robolectric-correctly in skydoves/android-testing-skills) into .claude/skills/using-robolectric-correctly in your project. Claude Code loads it when a task matches its description.
Run `npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a codex`. Or copy the skill folder (jvm-tests/robolectric/using-robolectric-correctly in skydoves/android-testing-skills) into .agents/skills/using-robolectric-correctly in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add skydoves/android-testing-skills --skill using-robolectric-correctly -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/using-robolectric-correctly, .gemini/skills/using-robolectric-correctly, .github/skills/using-robolectric-correctly and .opencode/skills/using-robolectric-correctly in your project.
SKILL.md names no scripts, command-line tools or credentials: Using Robolectric Correctly is instructions for the agent only.
SKILL.md names 3 domains. As links in the text: robolectric.org, developer.android.com and github.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Using Robolectric Correctly is published under the Apache-2.0 licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.4k tokens (SKILL.md is roughly 22k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Using Robolectric Correctly: Testing Skill (TypeCellOS/BlockNote, 10k stars), Mesh Lab (permissionlesstech/bitchat-android, 7.8k stars), Playwright Testing (chongdashu/vibejam-starter-pack, 149 stars) and Playwright Testing (chongdashu/vibejam-starter-pack, 149 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
skydoves (a GitHub user) maintains it in skydoves/android-testing-skills, which has 333 GitHub stars. The repository holds 50 skills in this directory. The repository was last updated on May 25, 2026.
Source: skydoves/android-testing-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.