Android: introduce unit tests and Kotlin - #38
Draft
ofalvai wants to merge 1 commit into
Draft
Conversation
ofalvai
force-pushed
the
push-rzlklynysuys
branch
from
August 18, 2026 18:20
dd451be to
3dd892e
Compare
ofalvai
force-pushed
the
push-rzlklynysuys
branch
from
August 19, 2026 19:29
3dd892e to
43475d9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
We want unit test coverage for the Android native module, and to be able to write new Android code (tests first, then prod code) in Kotlin instead of Java. This PR introduces both, wires the resulting tests into CI, and along the way upgrades this legacy module's Gradle/AGP/Kotlin toolchain since it needed it to support Kotlin properly.
What
Kotlin support (
android/app/build.gradle)buildscript { classpath ... } + apply pluginform, reading the version from the consumer's ownrootProject.ext.kotlinVersionfirst (most RN apps already set this for reanimated/gesture-handler/etc.) and falling back to a bundled default otherwise.kotlin-androidexplicitly. On AGP 9+, skips it — AGP 9 provides Kotlin support built in, and applying the classic plugin on top of it fails. This mirrors how other RN libraries adapted for AGP9 (see the patches linked from the AGP v9 adoption RFC).compileOptionsbumped to JVM 17 (from 11), matchingmise.tomland RN 0.76+'s own minimum JDK. Under AGP9's built-in Kotlin, the Kotlin JVM target defaults to this automatically (see Android's built-in Kotlin migration guide); below AGP9 it's still set explicitly viakotlinOptions.First unit test (
android/app/src/test/java/com/microsoft/codepush/react/CodePushUtilsTest.kt)CodePushUtils.appendPathComponent, written in Kotlin. Chosen as the first target because it's purejava.io.Filelogic with no RN-bridge JNI or Android-stub dependencies, so it runs under a plain JVM unit test without needing Robolectric.Modernized Gradle defaults (
android/app/build.gradlefallbacks only,android/build.gradle,android/gradle.properties,android/gradle/wrapper/gradle-wrapper.properties,Examples/CodePushDemo/android/gradle.properties)DEFAULT_COMPILE_SDK_VERSION/DEFAULT_BUILD_TOOLS_VERSION/DEFAULT_TARGET_SDK_VERSION/DEFAULT_MIN_SDK_VERSIONbumped to match RN's current template — these are fallbacks only, used solely when a consumer app doesn't set its own (which real RN apps always do), so this doesn't change behavior for any real consumer.compileSdkVersion(int)→compileSdk(AGP 9 removed the old method).android/build.gradle,gradle.properties, wrapper) bumped to AGP 9.2.1 / Gradle 9.4.1, and opts out of AGP9's built-in Kotlin/new DSL viaandroid.builtInKotlin=false/android.newDsl=false— matching what RN's own 0.87 app template does (this opt-out is itself temporary; it's removed starting AGP10). Applied the same opt-out toExamples/CodePushDemofor consistency.CI (
.github/workflows/ci-test.yml)android-unit-testjob: fast, runs onubuntu-latest, justnpm install+./gradlew :app:test.android-test(the slow emulator-based e2e suite) now depends onandroid-unit-test, so a broken unit test fails fast instead of waiting on the much slower e2e run.gradle/actions/setup-gradle@v6for dependency/build caching, shared across jobs in the same workflow run;android-testsetscache-read-only: truesince its Gradle build lives in a test app generated at runtime, not a project checked into the repo, so it has nothing meaningful to contribute back to the cache.Decisions
buildscript/apply pluginover theplugins { id(...) version(...) }DSL for Kotlin. The latter hard-fails the whole consumer build if any other subproject (e.g. reanimated) applies a different Kotlin version, since it enforces exact version matches per plugin id across the whole multi-project build. The classic form just resolves to a single version (highest wins), which is what a library consumed as live Gradle source via autolinking needs.agpMajor < 9) instead of probing for an already-registeredkotlinextension. Verified empirically that AGP9's built-in Kotlin compilation stays active even when a consumer opts out of the new DSL viaandroid.builtInKotlin=false, so the AGP version alone is enough to decide — no need to inspect what the consumer opted into. This is also the same pattern used by other RN libraries' AGP9 migration patches.android/app/build.gradlereaches downstream consumers. RN autolinking includes it as a live Gradle subproject inside the consumer's own build (not a prebuilt binary), so it's the only file inandroid/whose changes have any downstream effect. Everything else (android/build.gradle,gradle.properties, wrapper) governs only this repo's own standalone test harness.android-unit-testruns in its own fastubuntu-latestjob gating the slow emulator job vianeeds:, rather than sharing a job for Gradle-cache convenience — a broken unit test now fails in seconds instead of after the full e2e run.