Fix Kotlin and new ActivityTestRule : The @Rule must be public

I wrote a simple activity in Koltin which takes a user name as input and simply displays it in the TextView. Here’s how the activity looks like.

And the code for the activity is,

class MainActivity : AppCompatActivity() {

    var button: Button? = null
    var userNameField: EditText? = null
    var displayUserName: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initViews()
    }

    private fun initViews() {
        button = this.findViewById(R.id.set_user_name)
        userNameField = this.findViewById(R.id.name_field)
        displayUserName = this.findViewById(R.id.display_user_name)

        this.button!!.setOnClickListener({
            displayUserName!!.text ="Hello ${userNameField!!.text}!"
        })
    }
}

Now let’s try to write a test for this activity which verifies if the TextView displays the name correctly or not. My test class looks have the following code:

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @Rule var activityTestRule = ActivityTestRule(MainActivity::class.java)

    @Before
    fun setUp() {
    }

    @Test
    fun setUserName() {
        onView(withId(R.id.name_field)).perform(typeText("Vivek Maskara"))
        onView(withId(R.id.set_user_name)).perform(click())
        onView(withText("Hello Vivek Maskara!")).check(matches(isDisplayed()))
    }
}

When I run the test it fails with the following stack trace:

org.junit.internal.runners.rules.ValidationError: The @Rule 'activityTestRule' must be public.
at org.junit.internal.runners.rules.RuleMemberValidator$MemberMustBePublic.validate(RuleMemberValidator.java:222)
at org.junit.internal.runners.rules.RuleMemberValidator.validateMember(RuleMemberValidator.java:99)
at org.junit.internal.runners.rules.RuleMemberValidator.validate(RuleMemberValidator.java:93)
at org.junit.runners.BlockJUnit4ClassRunner.validateFields(BlockJUnit4ClassRunner.java:196)
at org.junit.runners.BlockJUnit4ClassRunner.collectInitializationErrors(BlockJUnit4ClassRunner.java:129)
.
.
.

The error is weird as the @Rule is already public.

JUnit allows to provide rules through a test class field or a getter method. What you annotated is in Kotlin a property though, which JUnit won’t recognise.

@Rule var activityTestRule = ActivityTestRule(MainActivity::class.java)

The annotation processor supports annotation targets and by default it uses the property target. In the above snippet it takes it as a property target unless specified otherwise.

First Approach

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

@get:Rule var activityTestRule = ActivityTestRule(MainActivity::class.java)

Second Approach

Kotlin also allows 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. The fix is to add a @JvmField annotation to it. Read more about java annotations here.

This works perfectly.

@Rule @JvmField var activityTestRule = ActivityTestRule(MainActivity::class.java)

I prefer the second approach and finally my test class looks like:

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @Rule @JvmField var activityActivityTestRule = ActivityTestRule(MainActivity::class.java)

    @Before
    fun setUp() {
    }

    @Test
    fun setUserName() {
        onView(withId(R.id.name_field)).perform(typeText("Vivek Maskara"))
        onView(withId(R.id.set_user_name)).perform(click())
        onView(withText("Hello Vivek Maskara!")).check(matches(isDisplayed()))
    }
}

You can buy me a coffee if this post really helped you learn something or fix a nagging issue!


Written on February 27, 2018 by Vivek Maskara.

Originally published on Medium

Vivek Maskara
Vivek Maskara
SDE @ Remitly

SDE @ Remitly | Graduated from MS CS @ ASU | Ex-Morgan, Amazon, Zeta

Related