Configuración y uso básico de mockk para Kotlin

Portada del artículo

1 Introducción

En el mundo de Kotlin existen diversas librerías para realizar mocking y stubbing a la hora de realizar pruebas en el software. Al ser interoperable con Java, librerías como mockito suelen ser utilizadas dentro de Kotlin.

Sin embargo, Kotlin dispone de alternativas nativas (implementadas en el propio lenguaje y no en Java) que son tanto implementaciones propias como adaptaciones de librerías existentes. Por ejemplo, Mockito tiene su propia versión nativa oficial denominada mockito-kotlin.

En este tutorial vamos a descubrir mockk, la cual es la librería nativa de mocking en Kotlin con mayor repercusión en Github (4.4k estrellas), la cual parece erigirse como librería de referencia en Kotlin. Es utilizado en la guía oficial de Spring-boot en Kotlin excluyendo a la propia mockito.

1.1 Configuración del entorno

Para la configuración de Kotlin y Maven revisa el archivo pom.xml del repositorio que se enlaza al final de este artículo. El entorno utilizado para desarrollar el artículo es el siguiente:

  • Open JDK 17
  • Kotlin 1.6.20
  • Maven 3.8.5
  • IntelliJ IDEA 2021.3.3 (Ultimate Edition) como entorno de desarrollo

1.2 Dominio del problema

Se ha creado un pequeño dominio para ilustrar el uso de la librería. La clase de dominio principal será

Text
Task

y representa una Tarea, que contiene una descripción, una fecha de expiración y un «check» de realizada.

// Definición de tarea
class Task(
    val text: String,
    val expirationDate: LocalDate = LocalDate.now().plusDays(7),
    var checked: Boolean = false
)

// Repositorio de tareas
interface TaskRepository {
    fun saveTask(task: Task)
    fun updateTask(task: Task)
    fun findAllTasks(): List
}

En el repositorio, el código está organizado de la siguiente manera:

  • En
    Text
    kotlin/src

    :

    • com.autentia.domain: Clases del dominio
      Text
      Task

      .

    • com.autentia.repository: Repositorios de entidades
      Text
      TaskRepository

      .

    • com.autentia.usecase: Implementaciones de casos de uso del dominio.
  • En
    Text
    kotlin/test
    • com.autentia: Test unitarios

2 Uso de mockk

En este apartado utilizaremos algunas de las opciones que proporciona mockk para implementar los test de los casos de uso. Para conocer todas las opciones es aconsejable visitar su documentación oficial.

2.1 Configuración

Para añadir mockk a nuestro proyecto es suficiente con añadir la dependencia con ámbito

Text
test

a nuestro gestor de dependencias, en nuestro caso como hijo del elemento

Text
dependencies

en pom.xml de la siguiente manera:

<!-- En el momento de hacer este tutorial la versión es la 1.12.3 -->
<dependency>
    <groupId>io.mockk</groupId>
    <artifactId>mockk</artifactId>
    <version>${mockk.version}</version>
    <scope>test</scope>
</dependency>

2.2 Caso de uso: Creación de una tarea

Vamos a comenzar creando el primer caso de uso de nuestro dominio, el cual trata de añadir una tarea al repositorio de tareas. Para ello crearemos la clase

Text
CreateTaskUseCase

y su correspondiente test unitario

Text
CreateTaskUseCaseTest

.

Identificamos rápidamente que vamos a necesitar añadir el repositorio de tareas

Text
TaskRepository

como dependencia del caso de uso, por lo que lo añadimos a la clase. Esto va a permitir añadir la dependencia mockeada al sujeto de la prueba, que será el caso de uso.

Simplificando, nuestra clase del caso de uso quedaría de la siguiente manera:

 class CreateTaskUseCase(val taskRepository: TaskRepository)

A continuación, en nuestro test vamos a utilizar mockk para mockear la dependencia con el repositorio y para verificar que en el caso de uso se llama al método

Text
save

de

Text
TaskRepository

solo una vez con un objeto de tipo Task.

@Test
fun `use case should create task`() {
    val taskRepository = mockk()
    every { taskRepository.saveTask(any()) } returns Unit

    val taskText = "Realizar tutorial de mockk en adictosaltrabajo.com"

    val usecase = CreateTaskUseCase(taskRepository)
    val request = CreateTaskRequest(taskText)
    val createdTask = usecase.executeUseCase(request)

    verify(exactly = 1) { taskRepository.saveTask(any()) }
    assert(createdTask.text == taskText)
}

En cuanto a mocks, en este test:

  • Se define el mock de
    Text
    TaskRepository

    a través del método

    Text
    mockk()

    en modo estricto.

  • Utilizando
    Text
    every

    se establece que para cada vez que se ejecute el método

    Text
    saveTask

    se devuelva

    Text
    Unit

    (

    Text
    void

    en Kotlin) y no haga nada.

  • Utilizando
    Text
    verify

    , se establece que el método

    Text
    saveTask

    se debe llamar exactamente una vez

    Text
    (exactly=1)

    .

Se ha establecido el mock en modo estricto. Esto implica que se debe proporcionar comportamiento al método

Text
saveTask

, de otro modo el test fallará con una excepción similar a esta:

Text
io.mockk.MockKException: no answer found for: TaskRepository(#1).saveTask(com.autentia.domain.Task@7ee3d262)

El test falla al no proporcionar comportamiento al mock:

val taskRepository = mockk()
// every { taskRepository.saveTask(any()) } returns Unit
verify(exactly = 1) { taskRepository.saveTask(any()) }
// mockk no sabe qué hacer con el método saveTask

El test pasa sin proporcionar comportamiento explícitamente, aunque por defecto se establece un comportamiento «vacío»:

val taskRepository = mockk(relaxed = true)
// every { taskRepository.saveTask(any()) } returns Unit
verify(exactly = 1) { taskRepository.saveTask(any()) }
// mockk establece por defecto un comportamiento vacío para saveTask

Se podría afinar un poco más y hacer que mockk solo proporcione este comportamiento vacío por defecto a aquellos métodos que devuelvan el tipo

Text
Unit

con

Text
mockk(relaxedUnitFun = true)

, mientras que seguiría necesitando proporcionar comportamiento a aquellos que devuelvan un tipo distinto.

NOTA: Si simplemente se quiere dar un comportamiento vacío y devolver

Text
Unit

, en vez de usar

Text
every

se puede utilizar

Text
justRun

con el método en cuestión. Por ejemplo

Text
justRun { taskRepository.saveTask(any()) }

2.3 Caso de uso: Marcar tareas expiradas

Se va a implementar un caso de uso que consiste en marcar como completadas aquellas tareas expiradas del repositorio. Para ello creamos la clase

Text
CheckAllExpiredUseCase

y su correspondiente test

Text
CheckAllExpiredUseCaseTest

.

class CheckAllExpiredTasksUseCase(val taskRepository: TaskRepository)

Se utiliza nuevamente

Text
mockk

para mockear la dependencia con

Text
TaskRepository

. Esta vez, se proporciona un comportamiento a través de

Text
every

y

Text
justRun

para establecer que cada vez que se llame al método

Text
findAllTasks

se devuelva una lista con una tarea pendiente y con otra expirada, además de proporcionar comportamiento vacío a

Text
updateTask

. Entre otras cosas, en este test se define con

Text
verifyAll

que los métodos

Text
findAllTasks

y

Text
updateTask

deben ser llamados.

@Test
fun `use case should check expired task`() {
    val taskRepository = mockk()

    val tareaPendiente = Task("Tarea pendiente", LocalDate.now().plusDays(1))
    val tareaExpirada = Task("Tarea expirada", LocalDate.now().minusDays(1))

    every { taskRepository.findAllTasks() } returns listOf(
        tareaPendiente, tareaExpirada
    )

    justRun { taskRepository.updateTask(any()) }

    val useCase = CheckAllExpiredTasksUseCase(taskRepository)

    useCase.executeUseCase()

    verifyAll {
        taskRepository.findAllTasks()
        taskRepository.updateTask(tareaExpirada)
    }
}

Este test se podría afinar mucho más haciendo uso de las diferentes opciones que proporciona mockk. Con este test no tendríamos garantía de que la actualización de la instancia de

Text
Task

se ejecute después de la obtención. Por ello, se podría verificar que, además de llamarse todos los métodos en

Text
verifyAll

, se llaman en el orden específico en el que se establecen:

verifyOrder {
    taskRepository.findAllTasks()
    taskRepository.updateTask(tareaExpirada)
}

Además, en este caso se puede establecer que nunca se llame al método

Text
saveTask

, el cual en nuestro dominio implica la creación de una nueva tarea. Para conseguirlo, podemos marcar la creación del mock como

Text
relaxedUnitFun

y verificamos que

Text
saveTask

no fue llamado.

fun `use case should check expired task`() {
    val taskRepository = mockk(relaxedUnitFun = true)

    // se omite el resto por simplicidad

    verify(exactly = 0) {
        taskRepository.saveTask(any())
    }
}

La librería mockk también permite establecer una jerarquía de mocks que preparen el conjunto de datos que anteriormente hemos tenido que crear de manera concreta. Mediante la concatenación en

Text
every

de llamadas a

Text
mockk

se proporciona un comportamiento similar de ambas instancias de

Text
Task

utilizadas en el ejemplo anterior. Recuerda que hay que proporcionar comportamiento a todos los métodos.

@Test
fun `use case should check expired task with mocks`() {
    val taskRepository = mockk()

    every { taskRepository.findAllTasks() } returns listOf(mockk {
        every { text } returns "Tarea pendiente"
        every { expirationDate } returns LocalDate.now().plusDays(1)
        every { isExpired() } returns false
        justRun { check() }
    }, mockk {
        every { text } returns "Tarea expirada"
        every { expirationDate } returns LocalDate.now().minusDays(1)
        every { isExpired() } returns true
        justRun { check() }
    })

    justRun { taskRepository.updateTask(any()) }

    val useCase = CheckAllExpiredTasksUseCase(taskRepository)

    useCase.executeUseCase()

    verifyOrder {
        taskRepository.findAllTasks()
        taskRepository.updateTask(any())
    }
}

3 Conclusiones

  • La librería mockk proporciona un mecanismo de creación de mocks que se integra perfectamente en el ecosistema Kotlin.
  • Hemos aprendido el uso del método
    Text
    mockk

    y los diferentes modos de uso que proporciona.

  • Hemos aprendido a utilizar
    Text
    every

    para proporcionar comportamiento a los mocks.

  • Hemos aprendido a utilizar
    Text
    verify

    para establecer las condiciones de uso de los métodos de una clase mockeada.

  • Hemos aprendido a utilizar
    Text
    verifyAll

    y

    Text
    verifyOrder

    para establecer condiciones de ejecución y de orden de
    ejecución.

Anexos

Referencias

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad

Información básica acerca de la protección de datos

  • Responsable: IZERTIS S.A.
  • Finalidad: Envío información de carácter administrativa, técnica, organizativa y/o comercial sobre los productos y servicios sobre los que se nos consulta.
  • Legitimación: Consentimiento del interesado
  • Destinatarios: Otras empresas del Grupo IZERTIS. Encargados del tratamiento.
  • Derechos: Acceso, rectificación, supresión, cancelación, limitación y portabilidad de los datos.
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad

Ingeniero de software por la Universidad de Murcia. Actualmente formo parte del equipo de desarrolladores de Autentia.

¿Quieres publicar en Adictos al trabajo?

Te puede interesar

02/03/2026

José Antonio Sánchez Segovia

Zephyr es un RTOS open source respaldado por la Linux Foundation que permite desarrollar dispositivos embebidos conectados, eficientes y escalables, facilitando el paso de prototipo a producto final con una arquitectura mantenible.

23/02/2026

Enrique Casado Díez

LoRa y LoRaWAN son tecnologías clave en el ecosistema IoT cuando se requiere largo alcance y bajo consumo energético. En este artículo analizamos su funcionamiento, Spreading Factor, link budget, arquitectura de red, frecuencias y clases de dispositivos, con un caso práctico real.

19/02/2026

Juan José Díaz Antuña

Copilot Chat es la forma más sencilla y segura de empezar a usar IA en Microsoft 365. En este artículo vemos cómo funciona, cómo activarlo y en qué se diferencia de Microsoft 365 Copilot, Copilot Studio y los Agentes Inteligentes, con ejemplos prácticos y una comparativa clara.