I’ve always liked creating simple annotation processors. There is something satysfing to see lot’s of code being generated for you while all it takes is just providing correct annotation to correct element and Et voila! That is of course if you trust compiler to generate correct code.


To verify generated code of my annotation processors I’ve usually created a module that would provide sample on how to correctly use the processor and how to use the generated code. This kinda worked for some time, but this didn’t help with catching unwanted behavior. And what we do if we want to have all edge cases under control ? Yes, we write tests!

Writing tests for generated code is something I’ve never done, so I was really surprised that I wasn’t able to somehow easily attach generated code for source Set and just reference that in my junit test cases. Fortunately one of my colleagues referenced me a library that would help with this.

tschuchortdev/kotlin-compile-testing

Enter Kotlin Compile Testing

Kotlin Compile Testing help writing unit tests for you annotation processor and can accept both Java and Kotlin source code, if you would like to test for that.

Now for a simple example. Let’s assume that we have an @IntSummable annotation that would generate an extensions function for annotated data class, which would sum all the integers parameters of primary constructor.

A test case to verify content of a generated file of a Foo class would look like this:

  @Test
  fun `validate file content for FooSummable`() {
    val kotlinSource = SourceFile.kotlin(
      "file2.kt", """
      package com.tests.summable
      
      import com.codegen.sample.IntSummable

          @IntSummable
          data class FooAlsoSummable(
            val bar: Int = 123,
            val baz: Int = 123
          )
    """
    )

    fun compileSource(source: SourceFile): KotlinCompilation.Result = 
      KotlinCompilation().apply {
        sources = listOf(source)
        annotationProcessors = listOf(SummableProcessor())
        workingDir = temporaryFolder.root
        inheritClassPath = true
        verbose = false
      }.compile()

    val compilationResult = compileSource(kotlinSource)

    Assertions.assertThat(compilationResult.exitCode)
      .isEqualTo(KotlinCompilation.ExitCode.OK)
    
    Assertions.assertThat(
      compilationResult.generatedFiles.find { it.name == "FooAlsoSummable.kt" }
    ).hasContent(
      """
         |package com.tests.summable
         |
         |import kotlin.Int
         |
         |fun FooAlsoSummable.sum(): Int {
         |  val sum = bar + baz
         |  return sum
         |}
      """.trimMargin()
    )
  }

Kotlin Compile Testing takes care of:

  • generating stubs
  • running apt
  • running kotlinc with generated code from previous steps
  • running javac with java sources and compiled Kotlin classes

You can of course access all of this files as properties of the KotlinCompilation.Result object that is returned as a result of compile() function.

There is also a ClassLoader instance provided as a part of KotlinCompilation.Result that points to generated source, but I was somehow unable to properly load my class and validate presence of my extension function at runtime (I’ll leave this for a future post).

I’ve also create a small sample module which also uses KotlinPoet for it’s fluent API:

lupajz/compile-testing-sample