Why don't you write unit tests and integration tests to ksp project
Tags: android, kotlin, programming
Published: 2023-01-11 18:30:49

# Background
[google/ksp](https://github.com/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)](https://github.com/chigichan24/Spider) for this article. The project is really simple ksp project. Spider is a [AGSL](https://developer.android.com/develop/ui/views/graphics/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](https://github.com/mockito/mockito-kotlin). 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](https://kotlinlang.org/docs/images/ksp-class-diagram.svg). 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uziiojf208f2xr320dm8.png)

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

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

// Spider will collect "FOO", "BAR" and "BAZ".
```
The unit test, successful case is [here](https://github.com/chigichan24/Spider/blob/main/spider/src/test/java/net/chigita/spider/fetcher/SpiderEnumValueFetcherTest.kt#L37-L60).
I also picked up here. 

```kotlin
@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
    )
}
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3tlx9uq9j9zrcvjg1c9.png)

# 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](https://github.com/tschuchortdev/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](https://github.com/chigichan24/Spider/blob/main/spider/src/test/java/net/chigita/spider/SpiderProcessorTest.kt) is an integration test for spider.

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

```kotlin
dependencies {
    testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.4.9")
    testImplementation("com.github.tschuchortdev:kotlin-compile-testing-ksp:1.4.9")
}
```

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

```kotlin
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)
    )
}
```

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

```kotlin
 private fun findGeneratedFiles(compilation: KotlinCompilation): List<File> {
    return compilation.kspSourcesDir
        .walkTopDown()
        .filter { it.isFile }
        .toList()
}
```

This approach is also used by square/moshi. You can also refer [this](https://github.com/square/moshi/blob/1fdc61dc311ed6a60f308be7246208f42bbc8b21/moshi-kotlin-codegen/src/test/java/com/squareup/moshi/kotlin/codegen/ksp/JsonClassSymbolProcessorTest.kt).

# 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](https://github.com/tschuchortdev/kotlin-compile-testing) instead.

Both unit test and integration test, we need to know how ksp is analyzing the source code. [Class diagram](https://kotlinlang.org/docs/images/ksp-class-diagram.svg) 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](https://github.com/chigichan24/Spider).

How about write tests to your own ksp projects?

# References
* https://kotlinlang.org/docs/ksp-overview.html
* https://kotlinlang.org/docs/images/ksp-class-diagram.svg
* https://github.com/google/ksp
* https://github.com/tschuchortdev/kotlin-compile-testing
* https://github.com/chigichan24/Spider
* https://github.com/square/moshi

"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."