Blog
KMP Kotlin May 26, 2026

@BeforeTest en commonMain de KMP: por qué no compila y cómo resolverlo

@BeforeTest falla en commonMain sin kotlin-test-annotations-common. Solución: ese artefacto en commonMain y kotlin-test-junit5 en androidMain como bridge JVM.

TL;DR: kotlin-test-annotations-common debe declararse en commonMain del módulo que exporta la clase base; kotlin-test-junit5 va en androidMain como bridge JVM. iOS no necesita nada adicional — el stdlib de Kotlin/Native ya incluye los actuals de kotlin.test.


Al migrar un módulo de testing compartido a Kotlin Multiplatform, la primera trampa aparece cuando se intenta usar @BeforeTest y @AfterTest en commonMain. El código parece correcto — los imports de kotlin.test existen, la clase compila en Android — pero en cuanto otros módulos consumen ese artefacto y ejecutan sus tests, todo falla.


El problema: @BeforeTest no llega a los consumidores

El contexto es un módulo :core:testing que exporta una clase base CoroutineTest con setup y teardown del dispatcher:

// core/testing/src/commonMain/kotlin/.../CoroutineTest.kt
import kotlin.test.AfterTest
import kotlin.test.BeforeTest

abstract class CoroutineTest {
    val testDispatcher = StandardTestDispatcher()

    @BeforeTest
    fun setupCoroutines() {
        Dispatchers.setMain(testDispatcher)
    }

    @AfterTest
    fun tearDownCoroutines() {
        Dispatchers.resetMain()
    }
}

Los imports de kotlin.test resuelven en el IDE sin problema porque el plugin de KMP inyecta el stdlib en commonMain. Pero el plugin no inyecta kotlin-test-annotations-common automáticamente cuando el módulo es una librería que otros van a consumir. Al ejecutar los tests en los módulos consumidores, el framework de test no reconoce los callbacks y el dispatcher no se inicializa.


La solución: dependencias en el lugar correcto

Hay que separar la dependencia en dos partes según el target:

// core/testing/build.gradle.kts
kotlin {
    sourceSets {
        commonMain.dependencies {
            api(kotlin("test-annotations-common"))  // expect declarations para @BeforeTest, @AfterTest, @Test
        }
        androidMain.dependencies {
            api(kotlin("test-junit5"))              // actual implementations sobre JUnit5
        }
    }
}

kotlin("test-annotations-common") contiene solo las anotaciones como expect declarations — sin implementación. Es lo que permite escribir @BeforeTest en commonMain y que el compilador lo entienda en cualquier target.

kotlin("test-junit5") es el bridge que conecta esas expect declarations con JUnit5 en el target JVM/Android. Sin él, el runner de JUnit no sabe que @BeforeTest equivale a @BeforeEach.


iOS: sin configuración adicional

En Kotlin/Native (targets iosX64, iosArm64, iosSimulatorArm64), los actuals de kotlin.test vienen incluidos en el stdlib del target. No hay que añadir ninguna dependencia extra en iosMain ni en iosTest.


El otro error silencioso: junit5.api en commonTest

Hay un segundo problema que suele aparecer junto al anterior: las dependencias de JUnit5 declaradas en commonTest de los módulos consumidores.

// ANTES — incorrecto
commonTest.dependencies {
    implementation(projects.core.testing)
    implementation(libs.junit5.api)         // ⚠️ JUnit5 no existe en iOS
    implementation(libs.junit5.extensions)
}

junit5.api y junit5.extensions son artefactos JVM puros. Declararlos en commonTest hace que Gradle intente resolverlos para el target iOS también, lo que genera errores de resolución de dependencias. La corrección es moverlos a androidUnitTest:

// DESPUÉS — correcto
commonTest.dependencies {
    implementation(projects.core.testing)
    // sin JUnit5 aquí
}
androidUnitTest.dependencies {
    implementation(libs.junit5.api)
    implementation(libs.junit5.extensions)
    implementation(libs.junit5.engine)
}

Los módulos consumidores que heredan CoroutineTest ya reciben el bridge JUnit5 transitivamente desde androidMain de :core:testing vía api(kotlin("test-junit5")). Solo hace falta el engine en androidUnitTest para que el runner los descubra.


Resumen

DependenciaDónde vaPor qué
kotlin("test-annotations-common")commonMain de :core:testingDeclara @BeforeTest, @AfterTest como expect
kotlin("test-junit5")androidMain de :core:testingBridge JVM: conecta expect con JUnit5
kotlin("test")commonTest del consumidorIncluye todo para el source set de tests
junit5.api, junit5.extensionsandroidUnitTest del consumidorSolo JVM, no pueden ir en commonTest
iOSnadaLos actuals vienen en el stdlib de Kotlin/Native