DEV Community

Cover image for Why don't you write unit tests and integration tests to ksp project
Kazuki Chigita
Kazuki Chigita

Posted on

Why don't you write unit tests and integration tests to ksp project

Background

google/ksp is one of the lightweight compiler plugin. We can easy to create compiler plugin with simple syntax. It's set as stable last year.

For now, some libraries (room, moshi, dagger and etc...) are supporting ksp.

When you create ksp project, you need to guarantee the behavior of the developing plugin. One solution is applying to demo project and confirm the actual behavior. But in the basic development in this few years, we'll prepare the test codes for the new implementation.

In this article, I'll explain how to write unit test and integration test for ksp project.

Demo project (Spider)

I've prepared the demo project(Spider) for this article. The project is really simple ksp project. Spider is a AGSL wrapper library for compose function.We can avoid to write template AGSL loading code.

Please refer this repository if you need to figure out the detail context.

Unit Test

For writing unit test, there are no special difference. You can mock & stub the behavior, after that verify the method internal state or(and) method return value.

In the demo project, I used junit and mockito. One thing we need to pre-learn do the unit test, that is ksp class diagram.

Here is a part of overview of class diagram. You can check whole dependencies here. Basically, KSDeclaration is root gateway. Each components like class declaration, function declaration, property declaration or other one extends KSDeclaration. And each it has some type and specific information. Each one are defined as interface. Therefore when you test the component, you can mock each declaration simply.

ksp class diagram

Let's see Spider case. Spider has a feature which collect enum property names that is annotated @AGSL_ENUM as class annotation.

@AGSL_ENUM
enum class AgslDefAssets{ FOO, BAR, BAZ }

// Spider will collect "FOO", "BAR" and "BAZ".
Enter fullscreen mode Exit fullscreen mode

The unit test, successful case is here.
I also picked up here.

@Mock
private lateinit var resolver: Resolver

private lateinit var target: SpiderEnumValueFetcher

@Before
fun setUp() {
    target = SpiderEnumValueFetcher(ANNOTATION)
}

@Test
fun testFetch() {
    val ksName = Mockito.mock(KSName::class.java).stub {
        on { getShortName() } doReturn "TEST"
    }
    val ksDeclaration = Mockito.mock(KSDeclaration::class.java).stub {
        on { simpleName } doReturn ksName
    }
    val ksClassDeclaration = Mockito.mock(KSClassDeclaration::class.java).stub {
        on { classKind } doReturn ClassKind.ENUM_CLASS
        on { declarations } doReturn sequenceOf(ksDeclaration)
    }

    resolver.stub {
        on { getSymbolsWithAnnotation(ANNOTATION) } doReturn sequenceOf(
            ksClassDeclaration
        )
    }
    val result = target.fetch(resolver)
    assertContentEquals(
        sequenceOf("TEST"),
        result
    )
}
Enter fullscreen mode Exit fullscreen mode

Enum class property is interpreted as below structure by ksp. So in the unit test, we'll follow the structure and mock each components.

enum class interpreted structure

Integration Test

In the unit test section, I wrote unit test way especially for ksp analyze part. In this section, I'll present about integration test way.

Strictly, it's not android integration test. In this section I defined integration test as ksp processing result confirmation.
Unfortunately, there're no official support that for testing SymbolProcessorProvider. But we can apply similar test by using kotlin-compile-testing. This library supports ksp so we can easily do integration test. This library is used by multiple ksp project such as room, moshi and etc...

Let's consider with Spider case. Here is an integration test for spider.

First of all, we'll add dependency for build.gradle.kts. Preparation is that's all.

dependencies {
    testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.4.9")
    testImplementation("com.github.tschuchortdev:kotlin-compile-testing-ksp:1.4.9")
}
Enter fullscreen mode Exit fullscreen mode

In the each test case, run ksp symbol processor. In the spider, I prepared the compile method.

private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation =
    KotlinCompilation()
        .apply {
            workingDir = temporaryFolder.root
            inheritClassPath = true
            symbolProcessorProviders = listOf(SpiderProcessorProvider())
            sources = sourceFiles.asList()
            verbose = false
            kspIncremental = true
        }

private fun compile(vararg sourceFiles: SourceFile): KspCompileResult {
    val compilation = prepareCompilation(*sourceFiles)
    val result = compilation.compile()
    return KspCompileResult(
        result,
        findGeneratedFiles(compilation)
    )
}
Enter fullscreen mode Exit fullscreen mode

We can also refer the generated file result. KotlinCompilation can reach to kspSources. So I prepared below util methods.

 private fun findGeneratedFiles(compilation: KotlinCompilation): List<File> {
    return compilation.kspSourcesDir
        .walkTopDown()
        .filter { it.isFile }
        .toList()
}
Enter fullscreen mode Exit fullscreen mode

This approach is also used by square/moshi. You can also refer this.

Conclusion

In this article, I presented about how to write unit test and integration test. Important point is about integration test. There are no test support by officially. We have to use kotlin-compile-testing instead.

Both unit test and integration test, we need to know how ksp is analyzing the source code. Class diagram is powerful helper for figuring out structure and dependency of each classes.

I've prepared unit tests and integration tests for demo ksp project. Please take a look this repository.

How about write tests to your own ksp projects?

References

"The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License."

Top comments (0)