Search code examples
kotlinjunit4

"DataPoint field samples must be public" error when using JUnit 4 Theories in Kotlin


Overview

Applying @DataPoint or @DataPoints to a property in a Kotlin class results in "DataPoint field samples must be public" error.

Description

Theories in JUnit4 use @DataPoint and @DataPoints annotations to mark sample data which is collected and passed to individual tests that can take them as arguments. When these annotations are applied to Kotlin properties (which are also necessarily annotated @JvmStatic and declared in companion objects), the test cases fail with the error "DataPoint field samples must be public". Kotlin properties are supposed to be public by default. Moreover, explicitly adding a "public" modifier has no affect.

Sample Code

import org.junit.runner.RunWith
import org.junit.experimental.theories.Theories
import org.junit.experimental.theories.DataPoints
import org.junit.experimental.theories.Theory
import org.junit.Assume.*

import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.*

@RunWith(Theories::class)
class SampleTheories {
    companion object {
        @JvmStatic
        @DataPoints
        public val samples = listOf(
            -8, -1, 0, 1, 2, 4, 8
        )
    }

    @Theory
    fun triangleInequality(a:Int, b:Int) {
        assumeThat(a, `is`(greaterThan(0)))
        assumeThat(b, `is`(greaterThan(0)))

        assertThat(a+b, `is`(greaterThan(a)))
        assertThat(a+b, `is`(greaterThan(b)))
    }
}

Gradle dependencies

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.51"
    // ...
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.hamcrest:hamcrest-library:1.3'
    // ...
}

Other system info

  • Android Studio 3.1.3
  • gradle 4.4
  • Android gradle plugin 3.1.3

Solution

  • Under the hood, public properties in Kotlin have a private backing field and public accessors. On the Java side (which is where JUnit4 operates), Kotlin properties do not exist as such. Instead, the fields and accessor methods must be used explicitly.

    The error arises because the annotation is applied to the (private) field, rather than the (public) accessor.

    There are a couple resolutions:

    1. Apply the @DataPoints annotation to the property's getter:

      @JvmStatic
      val samples = listOf(
          -8, -1, 0, 1, 2, 4, 8
      ) @DataPoints get
      

      This will only evaluate the expression once.

    2. Declare the data points as a method, rather than a property:

      @JvmStatic
      @DataPoints
      fun samples = listOf(
          -8, -1, 0, 1, 2, 4, 8
      )
      

      Since the data point source is a function, each time they're retrieved (which may not be many times; I'm not familiar with the internals of Theories), the expression will be re-evaluated.