Ir al contenido principal
Versión: 6.0

Montables

[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Los montables son un tipo especial de extensión que se puede instalar en una spec, devolviendo lo que se denomina un valor materializado al llamador. Esto permite que estas extensiones devuelvan objetos de control distintos de la propia extensión. El montable puede personalizarse durante su creación mediante un bloque de configuración opcional.

Los montables se instalan usando la función install en el nivel superior de una spec.

Por ejemplo, podríamos imaginar una extensión de Kafka que devuelve un cliente Kafka una vez instalada.

class MyExampleTest : FunSpec() {
init {
val kafka = install(EmbeddedKafka) {
port = 9092
}
}
}

Aquí kafka sería el valor materializado y, en este ejemplo, podría ser un consumidor de Kafka conectado a la instancia de Kafka integrada. El bloque de configuración se usa para configurar la extensión, y EmbeddedKafka es la propia extensión.

Los montables pueden, por supuesto, devolver la propia extensión si no necesitan un objeto separado, o devolver Unit.

Otro ejemplo de montable es la extensión JDBC Test Container, que devuelve un grupo de conexiones JDBC como valor materializado. Esto hace muy conveniente el uso de test containers en Kotest.

Creación de un montable

Implementa la interfaz MountableExtension y además implementa cualquier otro método de ciclo de vida que necesites. Esta es otra característica potente de los montables, ya que permite que tu extensión se enlace a otros eventos del ciclo de vida. Por ejemplo, tu instancia montable puede implementar AfterSpec, y entonces el método afterSpec del montable se llamará después de que la spec haya terminado de ejecutarse.

La interfaz tiene dos parámetros de tipo: el tipo de configuración y el tipo de valor materializado. Este último es el tipo que se devuelve al llamador y, como se mencionó antes, puede ser el mismo que la extensión. El tipo de configuración se pasa como objeto receptor al bloque de configuración. Aquí es donde defines valores o métodos que quieres que los llamadores puedan asignar o invocar.

precaución

Un inconveniente es que el bloque de configuración no es suspendible, debido a que el bloque init en sí mismo no es suspendible. Para solucionarlo, puedes usar runBlocking { } dentro de tu implementación.

Ejemplo

Vamos a crear un montable que instale una base de datos integrada H2. El valor materializado será una conexión a la instancia de la base de datos. Permitiremos al usuario configurar algunos detalles de la base de datos en un bloque de configuración. También implementaremos AfterSpec para que la base de datos se cierre después de que la spec termine de ejecutarse.

Primero creamos la clase de configuración que contiene nuestras opciones de personalización.

class H2Config {
var databaseName = "test"
}

Ahora creamos la extensión montable, implementando también AfterSpec para poder cerrar la conexión después de que la spec haya terminado de ejecutarse. Observa que usamos la función mount para realizar la lógica de instalación, y esta función devuelve el valor materializado - en este caso, una conexión JDBC a la base de datos.

class H2DatabaseMountableExtension() : MountableExtension<H2Config, Connection>, AfterSpecListener {

var conn: Connection? = null

override fun mount(configure: H2Config.() -> Unit): Connection {
val config = H2Config()
config.configure()
conn = DriverManager.getConnection("jdbc:h2:~/${config.databaseName}")
return conn!!
}

override suspend fun afterSpec(spec: Spec) {
conn?.close()
}
}