Kotlin
ActivityTestRule
Testing
Android Development
JUnit

Kotlin and new ActivityTestRule The Rule must be public

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

The rule must be public error usually appears because JUnit 4 is inspecting Kotlin test code through Java reflection. The Kotlin property may look correct in source code, but the generated Java-visible shape is what matters, so the fix is usually to expose the rule through @get:Rule or @JvmField in the form JUnit expects.

Why Kotlin and JUnit Disagree

In Java, a JUnit rule is commonly declared as a public field. In Kotlin, a property is not automatically the same thing as a plain Java field. It usually becomes a private backing field plus public accessors.

That difference matters because JUnit rule discovery is not looking at Kotlin semantics. It is looking at Java reflection targets.

So code that compiles cleanly can still fail at runtime because JUnit does not see the rule in the expected public form.

The Usual Kotlin Fix: @get:Rule

For Kotlin tests, the most common pattern is to annotate the generated getter explicitly.

kotlin
1import androidx.test.ext.junit.rules.ActivityScenarioRule
2import androidx.test.ext.junit.runners.AndroidJUnit4
3import org.junit.Rule
4import org.junit.Test
5import org.junit.runner.RunWith
6
7@RunWith(AndroidJUnit4::class)
8class MainActivityTest {
9
10    @get:Rule
11    val activityRule = ActivityScenarioRule(MainActivity::class.java)
12
13    @Test
14    fun titleIsVisible() {
15        // test code here
16    }
17}

This works because JUnit can see a public getter carrying the rule annotation.

Field-Based Alternative: @JvmField

Older examples often use a field-style declaration instead. In that pattern, @JvmField removes the accessor indirection so JUnit sees a Java field.

kotlin
1import androidx.test.rule.ActivityTestRule
2import org.junit.Rule
3
4class LegacyMainActivityTest {
5
6    @Rule
7    @JvmField
8    val activityRule = ActivityTestRule(MainActivity::class.java)
9}

This style is still valid. The key point is that @get:Rule and @Rule @JvmField are two different ways to give JUnit a Java-visible rule.

ActivityTestRule Versus ActivityScenarioRule

If you are already touching legacy instrumentation tests, it is worth asking whether ActivityTestRule should still be used at all. In many AndroidX codebases, ActivityScenarioRule is the more modern choice.

Legacy style:

kotlin
@Rule
@JvmField
val legacyRule = ActivityTestRule(MainActivity::class.java)

Modern style:

kotlin
@get:Rule
val scenarioRule = ActivityScenarioRule(MainActivity::class.java)

The visibility fix is one issue. Modernizing the rule type may be another.

Check the Test Dependencies Too

If the syntax looks right and the error still persists, inspect the AndroidX test dependencies. Mixed old support-test libraries and AndroidX libraries can produce confusing results that look like a Kotlin visibility problem.

gradle
1androidTestImplementation "androidx.test.ext:junit:1.1.5"
2androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
3androidTestImplementation "androidx.test:rules:1.5.0"
4androidTestImplementation "androidx.test:runner:1.5.2"

Keep the runner, rules, and integration libraries aligned.

A Safe Fix Sequence

When inheriting a failing legacy suite, change one class first instead of rewriting every rule declaration at once:

  1. convert one failing rule to @get:Rule
  2. rerun the instrumentation tests
  3. if needed, inspect the dependency stack
  4. only then migrate old ActivityTestRule usages more broadly

That incremental approach makes it much easier to tell whether the real cause was annotation targeting, bytecode shape, or stale AndroidX test dependencies.

Common Pitfalls

  • Writing a normal Kotlin property with @Rule and assuming JUnit will treat it exactly like a public Java field.
  • Assuming @JvmField is always required; it depends on whether you are using a field-based or getter-based pattern.
  • Fixing the annotation target while leaving a mixed or outdated Android test dependency stack in place.
  • Modernizing every legacy rule in one large sweep instead of fixing one failing pattern at a time.
  • Treating the error as a Kotlin visibility bug only, when it is really about Java reflection shape.

Summary

  • The rule must be public error is usually about how Kotlin exposes the rule to JUnit 4 through Java reflection.
  • '@get:Rule is the most common Kotlin-friendly pattern.'
  • '@Rule @JvmField is the field-based alternative, especially in older code.'
  • 'ActivityScenarioRule is often a better modern choice than ActivityTestRule.'
  • If the syntax is correct and the error remains, inspect the test dependency stack next.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.