Introdução: Aquele erro que enlouquece
\n\nVocê já escreveu um teste em Kotlin para Android, adicionou ActivityTestRule, executou — e de repente viu aquela mensagem gelada: "The @Rule must be public"? Se sim — você não está sozinho. Esta pergunta no Stack Overflow acumulou milhares de visualizações, e todos os dias dezenas de desenvolvedores pisam no mesmo rastelo.
O problema parece simples, mas em Kotlin ele se transforma em uma verdadeira aventura. Em Java tudo funciona perfeitamente: @Rule, campo público — e pronto. Já em Kotlin… bem, você sabe. Hoje vamos entender por que isso acontece e, principalmente, como corrigir isso de uma vez por todas.
A propósito, se você prefere Python, no PythonLib também tem muito conteúdo útil sobre testes. Mas hoje — é Kotlin puro e Android.
\n\nPor que ocorre o erro "The @Rule must be public"?
\n\nVamos lembrar como funciona o ActivityTestRule no JUnit. Esta é uma regra (Rule) que inicia a Activity antes de cada teste e a encerra corretamente após. Em Java você escreve:
@Rule\npublic ActivityTestRule<MainActivity> activityTestRule =\n new ActivityTestRule<>(MainActivity.class);\n\n\nTudo simples: campo público, regra — public. Já em Kotlin, por padrão, tudo é public, mas aqui está o truque. Quando você escreve:
\n\n@Rule\nval activityTestRule = ActivityTestRule(MainActivity::class.java)\n\n\n…o Kotlin gera um campo privado e um getter público. Mas o JUnit exige exatamente um campo público, e não um getter. Essa é toda a mágica.
\n\nO erro "The @Rule must be public" aparece justamente porque o JUnit não enxerga seu campo — ele é privado.
Solução nº 1: Usar @get:Rule (a mais popular)
\n\nA maneira mais simples e comum é aplicar a anotação não ao campo, mas ao getter. Para isso, em Kotlin existe a sintaxe @get:Rule:
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\nO que acontece aqui? Dizemos: "Anote o getter, não o campo". O JUnit vê o getter público, obtém acesso à regra, e tudo funciona. Testado por centenas de desenvolvedores.
\n\nEsta solução é seu "plano A". É concisa, clara e não requer bibliotecas adicionais.
\n\nSolução nº 2: Tornar o campo público com @JvmField
\n\nSe você não gosta da sintaxe @get:Rule (embora não devesse), pode usar a anotação @JvmField. Ela força o Kotlin a gerar um campo público sem getter:
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\nFunciona exatamente como o código Java. Desvantagem — um pouco mais de código. Vantagem — você indica explicitamente que o campo deve ser público.
\n\nPessoalmente, prefiro @get:Rule — é mais curto e parece mais natural para um desenvolvedor Kotlin. Mas a escolha é sua.
Solução nº 3: Usar ActivityScenarioRule (forma moderna)
\n\nAgora vamos falar sobre o que o Google recomenda em vez de ActivityTestRule. Na biblioteca androidx.test.ext:junit existe o ActivityScenarioRule. Ele é mais flexível e se integra melhor com os testes Android modernos.
Com ele, a mesma história — é necessário usar @get:Rule ou @JvmField:
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