Introducción: Por qué las pruebas avanzadas son la base del código Kotlin confiable
En el desarrollo moderno con Kotlin, escribir código es solo la mitad del trabajo. La verdadera maestría se demuestra en la capacidad de crear sistemas confiables, comprobables y fáciles de mantener. Las pruebas unitarias básicas son conocidas por todos, pero cuando el proyecto crece hasta una arquitectura de microservicios o una lógica de negocio compleja, las comprobaciones simples ya no son suficientes. Las pruebas avanzadas incluyen no solo el aislamiento de clases, sino también la verificación de la interacción entre componentes (pruebas de integración) y la reproducción completa de escenarios de usuario (E2E).
Este artículo está dirigido a desarrolladores que ya están familiarizados con los fundamentos de Kotlin y desean llevar la calidad de sus proyectos al siguiente nivel. Analizaremos tres niveles clave de pruebas: unit, integration y e2e, utilizando herramientas modernas: MockK, Testcontainers y Ktor Client. Aprenderá a escribir pruebas que no solo "pasen", sino que realmente protejan su código contra regresiones.
1. Pruebas unitarias: Aislamiento y mocks con MockK
Las pruebas unitarias son la base de la pirámide de pruebas. A nivel avanzado, no solo verificamos funciones, sino que probamos interacciones complejas entre objetos utilizando mocks. En Kotlin, la mejor biblioteca para esto es MockK.
1.1. Funcionalidades avanzadas de MockK
A diferencia de Mockito, MockK está escrito específicamente para Kotlin y admite corrutinas, lambdas y funciones de extensión. Veamos un ejemplo de prueba de un servicio que utiliza un repositorio y corrutinas:
// Servicio a probarclass UserService(private val repo: UserRepository) { suspend fun getUserName(id: Long): String { val user = repo.findById(id) return user?.name ?: "Unknown" }}
// Pruebaclass UserServiceTest { private val repo = mockk<UserRepository>() private val service = UserService(repo)
@Test fun `debería devolver el nombre del usuario cuando el usuario existe`() = runTest { // given coEvery { repo.findById(1L) } returns User(1, "Alice")
// when val result = service.getUserName(1L)
// then assertEquals("Alice", result) coVerify(exactly = 1) { repo.findById(1L) } }
@Test fun `debería devolver Unknown cuando el usuario no se encuentra`() = runTest { // given coEvery { repo.findById(any()) } returns null
// when val result = service.getUserName(99L)
// then assertEquals("Unknown", result) }}Puntos clave:
coEveryycoVerify— para trabajar con funciones suspend.mockk()crea un mock vacío,spyk()— un mock parcial de un objeto real.- Use
slotspara capturar argumentos.
1.2. Pruebas de lambdas y funciones de extensión
MockK permite simular funciones de extensión, lo cual es crítico para frameworks como Ktor o Spring WebFlux:
class StringValidator { fun String.isValidEmail(): Boolean = this.contains("@")}
// Prueba@Testfun `prueba de mock de extensión`() { val validator = mockk<StringValidator>() with(validator) { every { "test@mail.com".isValidEmail() } returns true every { "invalid".isValidEmail() } returns false } assertTrue("test@mail.com".isValidEmail()) assertFalse("invalid".isValidEmail())}2. Pruebas de integración: Base de datos y Testcontainers
Las pruebas de integración verifican cómo su código interactúa con dependencias reales: bases de datos, colas de mensajes, API externas. Aquí es donde Testcontainers viene al rescate: una biblioteca que levanta contenedores Docker directamente durante las pruebas.
2.1. Configuración de Testcontainers con PostgreSQL
Para una aplicación Spring Boot, se ve así:
Blogs
Book Recommendations
Leer también
Expresiones regulares en JavaScript con ejemplos para Node.js
Pruebas en C#: Unit, Integration, E2E — Guía completa para .NET
Archivos en Java: de principiante a experto en 20 minutos
Registro en aplicaciones Go: mejores prácticas y herramientas
Salarios de desarrolladores C++ en Rusia 2024: revisión completa del mercado