Skip to content

Testing the Processor

Traps that bite when authoring TestKspRunner-based tests in kraft-ksp/src/jvmTest. Both were hit in PR #74's CI run; check here before writing a new fixture.

1. Generated-file order is filesystem-dependent — never assert on generated.first()

Symptom: a test asserting on compileAndReturnGenerated(...).first().readText() passes locally on macOS but fails on Linux CI with output like:

expected to contain: fun Status.toStatusDto()
but was: // Generated by Kraft. Do not edit.
         package kraft.generated.registry
         ...

Cause: any fixture that produces a converter — a top-level @KraftConverter function or a @MapEnum mapping — generates two files: the mapper and the @KraftConverterDelegate registry (kraft/generated/registry/Converters_<hash>.kt). sourcesGeneratedBySymbolProcessor returns them in filesystem walk order, which differs between macOS and Linux (ext4 readdir order). On CI the registry can come first.

Fix: select generated files by name, never positionally:

val content = TestKspRunner.compileAndReturnGenerated(source)
    .first { "EnumMapper" in it.name }
    .readText()

or assert against all files joined:

val content = generated.joinToString("\n") { it.readText() }

Plain @MapConfig data-class fixtures generate a single file, so first() happens to be safe there — but the by-name form costs nothing and survives fixture growth.

Minimal reproduction: any of the three @MapEnum content tests (MapEnumAutoMappingTest etc.) with the name filter removed, run on a Linux runner.

2. Fixtures using converters need a package declaration

Symptom: generated code fails to compile inside the test (with kspWithCompilation = true):

e: .../EventDtoToEventMapper.kt.kt:12:30 Unresolved reference 'toIso'.
e: .../Converters_<hash>.kt:9:61 Unresolved reference 'toIso'.

Cause: the fixture declares a top-level @KraftConverter function without a package line, so it lands in the root package. Kotlin cannot import top-level declarations from the root package, so the generated mapper and delegate registry (which live in their own packages) cannot reference the converter.

Fix: give every fixture that declares a top-level @KraftConverter a package declaration:

val source = SourceFile.kotlin("Models.kt", """
    package fixtures

    @com.blu3berry.kraft.config.KraftConverter
    fun Stamp.toIso(): String = epochMillis.toString()
    ...
""")

Fixtures without converters can stay package-less — nothing generated needs to import from them across packages.

Minimal reproduction: TypeAliasMappingTest.'converter declared with alias receiver...' with its package aliased line removed.