Kotlin and new ActivityTestRule : The @Rule must be public

45,174

Solution 1

JUnit allows providing rules through a test class field or a getter method.

What you annotated is in Kotlin a property though, which JUnit won't recognize.

Here are the possible ways to specify a JUnit rule in Kotlin:

Through an annotated getter method

From M13, the annotation processor supports annotation targets. When you write

@Rule
public val mActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule(javaClass<MainActivity>())

though, the annotation will use the property target by default (not visible to Java).

You can annotate the property getter however, which is also public and thus satisfies JUnit requirements for a rule getter:

@get:Rule
public val mActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule(javaClass<MainActivity>())

Alternatively, you can define the rule with a function instead of a property (achieving manually the same result as with @get:Rule).

Through an annotated public field

Kotlin also allows since the beta candidate to deterministically compile properties to fields on the JVM, in which case the annotations and modifiers apply to the generated field. This is done using Kotlin's @JvmField property annotation as answered by @jkschneider.


Side note: be sure to prefix the Rule annotation with an @ character as it is now the only supported syntax for annotations, and avoid @publicField as it will soon be dropped.

Solution 2

With Kotlin 1.0.0+, this works:

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

Solution 3

Use this:

@get:Rule
var mActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule(MainActivity::class.java)

Solution 4

Instead of @Rule , you should use @get:Rule.

Solution 5

With Kotlin 1.0.4:

val mActivityRule: ...
    @Rule get
Share:
45,174

Related videos on Youtube

Geob-o-matic
Author by

Geob-o-matic

Rust/C/C++ software engineer

Updated on July 30, 2022

Comments

  • Geob-o-matic
    Geob-o-matic almost 2 years

    I'm trying to make UI test for my android app in Kotlin. Since the new system using ActivityTestRule, I can't make it work: it compiles correctly, and at runtime, I get:

    java.lang.Exception: The @Rule 'mActivityRule' must be public.
        at org.junit.internal.runners.rules.RuleFieldValidator.addError(RuleFieldValidator.java:90)
        at org.junit.internal.runners.rules.RuleFieldValidator.validatePublic(RuleFieldValidator.java:67)
        at org.junit.internal.runners.rules.RuleFieldValidator.validateField(RuleFieldValidator.java:55)
        at org.junit.internal.runners.rules.RuleFieldValidator.validate(RuleFieldValidator.java:50)
        at org.junit.runners.BlockJUnit4ClassRunner.validateFields(BlockJUnit4ClassRunner.java:170)
        at org.junit.runners.BlockJUnit4ClassRunner.collectInitializationErrors(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.ParentRunner.validate(ParentRunner.java:344)
        at org.junit.runners.ParentRunner.<init>(ParentRunner.java:74)
        at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:55)
        at android.support.test.internal.runner.junit4.AndroidJUnit4ClassRunner.<init>(AndroidJUnit4ClassRunner.java:38)
        at android.support.test.runner.AndroidJUnit4.<init>(AndroidJUnit4.java:36)
        at java.lang.reflect.Constructor.constructNative(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
        at android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder.buildAndroidRunner(AndroidAnnotatedBuilder.java:57)
        at android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder.runnerForClass(AndroidAnnotatedBuilder.java:45)
        at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
        at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29)
        at org.junit.runner.Computer.getRunner(Computer.java:38)
        at org.junit.runner.Computer$1.runnerForClass(Computer.java:29)
        at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
        at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:98)
        at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:84)
        at org.junit.runners.Suite.<init>(Suite.java:79)
        at org.junit.runner.Computer.getSuite(Computer.java:26)
        at android.support.test.internal.runner.TestRequestBuilder.classes(TestRequestBuilder.java:691)
        at android.support.test.internal.runner.TestRequestBuilder.build(TestRequestBuilder.java:654)
        at android.support.test.runner.AndroidJUnitRunner.buildRequest(AndroidJUnitRunner.java:329)
        at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:226)
        at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1584)
    

    Here is how I declared mActivityRule:

    RunWith(javaClass<AndroidJUnit4>())
    LargeTest
    public class RadisTest {
    
        Rule
        public val mActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule(javaClass<MainActivity>())
    
       ...
    }
    

    It is already public :/

    • Andrey Breslav
      Andrey Breslav about 9 years
      Currently, Kotlin does not support making fields that are backing properties public, but we are working on it
    • Geob-o-matic
      Geob-o-matic about 9 years
      Is there a bug tracking this?
    • Andrey Breslav
      Andrey Breslav about 9 years
  • Geob-o-matic
    Geob-o-matic over 8 years
    Does it? I still get the error and it is public with M14 (0.14.449)
  • Geob-o-matic
    Geob-o-matic over 8 years
    seems to need @publicField, but it is deprecated, so I tried lateinit as suggested by IDE, but then it barks at me saying lateinit can be applied only on mutable (the blog's article says otherwise…)
  • desseim
    desseim over 8 years
    Updated my answer ;) As for lateinit val, it has been removed in M14 since it didn't fully enforce the immutability of the field, see the [M14 release blog post](blog.jetbrains.com/kotlin/2015/10/kotlin-m14-is-out/).
  • Rob
    Rob over 8 years
    And it solved the problem of the lint tool complaining that the public was qualifier on the rule was redundant.
  • Jignesh Ansodariya
    Jignesh Ansodariya over 7 years
    This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
  • Praveer Gupta
    Praveer Gupta over 7 years
    @JigneshAnsodariya - I did a check and this answer provides an answer to what has been asked. More information on how this is working can be found at the following link - kotlinlang.org/docs/reference/…
  • makovkastar
    makovkastar over 6 years
    There is a problem with this approach. If you want to access the activity under test (for example: assertTrue(activityRule.activity.isFinishing)), the property getter will be called, thus creating a new rule. In this case activityRule.activity will be null.
  • aleksandrbel
    aleksandrbel almost 6 years
    What is @get: annotation? It also does not work in my situation.
  • Michał Ziobro
    Michał Ziobro over 5 years
    Yes \@JvmField is crucial or use rather \@get:Rule
  • Abhijit Sarkar
    Abhijit Sarkar over 5 years
    Only works on final fields. Won't work for a rule like var wireMockRule = WireMockRule(wireMockConfig().dynamicPort()).
  • Peter Chaula
    Peter Chaula about 5 years
    Care to add an explanation?
  • thegirlincode
    thegirlincode over 2 years
    After changing Rule to @get: Rule, it was fixed. Thanks