@Rule debe ser público en Kotlin: solución al problema con ActivityTestRule

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Ese error que vuelve loco a cualquiera

\n\n

¿Alguna vez has escrito un test en Kotlin para Android, añadido ActivityTestRule, lo has ejecutado y de repente has visto ese mensaje que hiela el alma: "The @Rule must be public"? Si es así, no estás solo. Esta pregunta en Stack Overflow ha acumulado miles de visitas, y cada día decenas de desarrolladores caen en la misma trampa.

\n\n

El problema parece simple, pero en Kotlin se convierte en una auténtica odisea. En Java todo funciona como la seda: @Rule, un campo público, y listo. Pero en Kotlin… bueno, ya sabes. Hoy vamos a entender por qué ocurre esto y, lo más importante, cómo solucionarlo de una vez por todas.

\n\n

Por cierto, si prefieres Python, en PythonLib también hay mucho contenido útil sobre testing. Pero hoy toca Kotlin y Android puro.

\n\n

¿Por qué aparece el error "The @Rule must be public"?

\n\n

Recordemos cómo funciona ActivityTestRule en JUnit. Es una regla (Rule) que inicia una Activity antes de cada test y la finaliza correctamente después. En Java escribes:

\n\n
@Rule\npublic ActivityTestRule<MainActivity> activityTestRule =\n    new ActivityTestRule<>(MainActivity.class);\n
\n\n

Todo simple: el campo es público, la regla es pública. En Kotlin, por defecto todo es público, pero aquí hay un truco. Cuando escribes:

\n\n
@Rule\nval activityTestRule = ActivityTestRule(MainActivity::class.java)\n
\n\n

…Kotlin genera un campo privado y un getter público. Pero JUnit exige exactamente un campo público, no un getter. Ahí está la magia.

\n\n

El error "The @Rule must be public" aparece precisamente porque JUnit no ve tu campo — es privado.

\n\n

Solución n.º 1: Usar @get:Rule (la más popular)

\n\n

La forma más sencilla y común es aplicar la anotación no al campo, sino al getter. Para ello, en Kotlin existe la sintaxis @get:Rule:

\n\n
class MainActivityTest {\n\n    @get:Rule\n    val activityTestRule = ActivityTestRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        assertNotNull(activityTestRule.activity)\n    }\n}\n
\n\n

¿Qué ocurre aquí? Le decimos: "Anota el getter, no el campo". JUnit ve el getter público, obtiene acceso a la regla, y todo funciona. Probado por cientos de desarrolladores.

\n\n

Esta solución es tu "plan A". Es concisa, clara y no requiere librerías adicionales.

\n\n

Solución n.º 2: Hacer el campo público con @JvmField

\n\n

Si no te gusta la sintaxis @get:Rule (aunque no debería ser así), puedes usar la anotación @JvmField. Esta obliga a Kotlin a generar un campo público sin getter:

\n\n
class MainActivityTest {\n\n    @JvmField\n    @Rule\n    val activityTestRule = ActivityTestRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        assertNotNull(activityTestRule.activity)\n    }\n}\n
\n\n

Funciona exactamente igual que el código Java. La desventaja es un poco más de código. La ventaja es que indicas explícitamente que el campo debe ser público.

\n\n

Personalmente prefiero @get:Rule — es más corto y se lee de forma más natural para un desarrollador Kotlin. Pero la decisión es tuya.

\n\n

Solución n.º 3: Usar ActivityScenarioRule (la forma moderna)

\n\n

Ahora hablemos de lo que Google recomienda en lugar de ActivityTestRule. En la librería androidx.test.ext:junit está ActivityScenarioRule. Es más flexible y se integra mejor con el testing moderno de Android.

\n\n

Con él pasa lo mismo: hay que usar @get:Rule o @JvmField:

\n\n
class MainActivityTest {\n\n    @get:Rule\n    val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        activityScenarioRule.scenario.onActivity { activity ->\n                

Blogs

Book Recommendations