Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing private methods in Kotlin

How to test private methods in Kotlin? I tried to add @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) from androidx.annotation.VisibleForTesting but it doesn’t make my function private

This is how I’m using it

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun doSomething() {}

[EDIT]

I understand that I shouldn't test private methods, but it's now always trivial. What about below case.

I have a CsvReader class

class CsvReader(private val inputStream: InputStream, private val separator: String = "\t") {
    fun read(): List<String> {
        return read(inputStream.bufferedReader())
    }
    private fun read(bufferedReader: BufferedReader): List<String> {
        val line = bufferedReader.use { it.readLine() } // `use` is like try-with-resources in Java
        return parse(line)
    }
    private fun parse(line: String): List<String> {
        return line.split(separator)
    }
}

And I wrote tests for it

class CsvReaderTest {
    private val stream = mock<InputStream>()
    private val reader = CsvReader(stream)
    private val bufferedReader = mock<BufferedReader>()
    @Test
    fun read() {
        whenever(bufferedReader.readLine()).thenReturn("Jakub\tSzwiec")
        reader.read(bufferedReader) shouldEqual listOf("Jakub", "Szwiec")
    }
    @Test
    fun readWhenEmpty() {
        whenever(bufferedReader.readLine()).thenReturn("")
        reader.read(bufferedReader) shouldEqual listOf("")
    }
    @Test
    fun throwIOExceptionWhenReadingProblems() {
        whenever(bufferedReader.readLine()).thenThrow(IOException::class.java)
        val read = { reader.read(bufferedReader) }
        read shouldThrow IOException::class
    }
}

Unfortunately, for tests I need to call private function fun read(bufferedReader: BufferedReader): List<String> because when mocking File, file.bufferedReader gives NullPointerException Unable to mock BufferedWriter class in junit

like image 510
qbait Avatar asked Aug 26 '18 22:08

qbait


People also ask

How does kotlin test private methods?

You can't directly test private methods, and you can't make a method private any other way than the keyword private . Either make them internal or only test public API.

Can private methods be tested?

The short answer is that you shouldn't test private methods directly, but only their effects on the public methods that call them. Unit tests are clients of the object under test, much like the other classes in the code that are dependent on the object.

How do you access private methods in test class?

Use the TestVisible annotation to allow test methods to access private or protected members of another class outside the test class. These members include methods, member variables, and inner classes. This annotation enables a more permissive access level for running tests only.


2 Answers

You can use java reflections:

To test the method:

class ClassUnderTest {
      private fun printGreetings(name: String): String {
        return "Hello, $name"
      }
}

That's enough:

  private val classUnderTest = spyk(ClassUnderTest()) 

  @Test
    fun `should return greetings`() {
      val method = classUnderTest.javaClass.getDeclaredMethod("printGreetings", String::class.java)
      method.isAccessible = true
      val parameters = arrayOfNulls<Any>(1)
      parameters[0] = "Piotr"

      assertEquals("Hello, Piotr", method.invoke(classUnderTest, *parameters) )
  }
like image 83
Piotr Avatar answered Sep 18 '22 08:09

Piotr


like this:

fun callPrivate(objectInstance: Any, methodName: String, vararg args: Any?): Any? {
        val privateMethod: KFunction<*>? =
            objectInstance::class.functions.find { t -> return@find t.name == methodName }

        val argList = args.toMutableList()
        (argList as ArrayList).add(0, objectInstance)
        val argArr = argList.toArray()

        privateMethod?.apply {
            isAccessible = true
            return call(*argArr)
        }
            ?: throw NoSuchMethodException("Method $methodName does not exist in ${objectInstance::class.qualifiedName}")
        return null
    }

you need to pass instance of the object you want the method to be called on, method name and required arguments

like image 25
Filip Pranklin Avatar answered Sep 18 '22 08:09

Filip Pranklin