Ver publicaciones por etiquetas

Ver publicaciones por categorías

Spring Boot con Kotlin — Creando tu Primera API

Spring Boot con Kotlin — Creando tu Primera API
SHARE

En este artículo aprenderás a crear tu primera API con Spring Boot y Kotlin.

Spring Framework

Spring Framework 🍃, el framework más popular en Java para el desarrollo de aplicaciones. Millones de desarrolladores usan Spring todos los días para crear todo tipo de aplicaciones. La primera versión de Spring en 2003 se centró en la inyección de dependencias, pero el framework ha crecido enormemente, el equipo de spring creó un conjunto de módulos que soportan casi todo lo que un desarrollador necesita para crear una aplicación. Dada tanta madurez, no es de extrañar que el framework se use en las principales empresas tecnológicas del mundo. Esa es la ventaja de un proyecto que ha estado en constante desarrollo y mejora durante casi 20 años.

Qué es Kotlin

Kotlin es un lenguaje creado por Jetbrains, la misma compañía que creó todos los increíbles IDE’s como IntelliJ Idea. JetBrains no estaba contento con Java ni con los otros lenguajes en el mercado y decidió crear Kotlin como una mejora sobre Java. Y cuando digo mejora es porque Kotlin fue diseñado para ser completamente compatible con Java, haciendo posible tener una base de código compartida entre estos dos lenguajes, permitiendo a las empresas hacer una migración gradual desde Java.

Kotlin se ha vuelto muy popular entre los desarrolladores y según la encuesta de StackOverflow de 2020, Kotlin es uno de los lenguajes más amados. Todo este amor no es por nada. Kotlin tiene muchas características que hacen el desarrollo más fácil, menos verboso y con mayor resistencia a errores. Una de las mejores funcionalidades es un sistema de seguridad nativa contra null, clases de extensiones, funciones de alto orden y la versión async await del lenguaje, llamada coroutines.

Creando el proyecto

Para empezar a crear nuestro proyecto, utilizaremos la herramienta Spring Initializer. Puedes encontrarla en start.spring.io. Esta es una herramienta creada por el equipo de Spring y es muy útil porque podemos hacer nuestra configuración inicial e incluir nuestras dependencias principales.

Pantalla de Spring initializer

Primero, puedes encontrar el código fuente completo aquí en mi Github. Para este proyecto, usaremos una configuración un poco diferente de los otros tutoriales comunes que podemos encontrar con Spring Boot. Vamos a usar Gradle como nuestra herramienta de construcción, pero por supuesto, como puedes ver, Maven también es una opción. Elegiremos la versión más estable de Spring, que es la 2.6.0 (cuando estoy escribiendo este artículo). Puedes cambiar los metadatos del proyecto si lo deseas o simplemente dejar los valores predeterminados. Elegí Java 11 porque todavía no tengo Java 17 instalado, pero no hará ninguna diferencia para este proyecto.

En la columna derecha, tenemos la opción de incluir nuestras dependencias iniciales. Para este proyecto, incluiremos algunas de ellas y más tarde añadiremos otras dependencias manualmente en nuestro archivo Gradle. Empecemos incluyendo:

  • Spring Boot DevTools — Tiene algunas características agradables que pueden ayudarnos durante el desarrollo, como recarga automática, recarga en vivo y desactivación de caché. Muy útil durante el desarrollo.
  • Spring Web — Necesitaremos crear nuestras API’s
  • Spring JPA y H2 Database — para guardar nuestros datos

Dependencias de Spring initializer

Como puedes ver en mis metadatos, voy a crear un ejemplo simulando un banco y haremos una API de transferencias. 💰

IntelliJ Idea

Descomprime el archivo y ábrelo en tu IDE. Yo usaré IntelliJ Idea Community Edition. Puedes descargarlo de forma gratuita o usar otro IDE. Algo que es bueno mencionar es que si tienes la versión ultimate de IntelliJ Idea, la herramienta Spring Initialiser que acabamos de ver ya está integrada en el IDE, por lo que puedes hacer toda la creación del proyecto directamente allí.

Gradle file

Lo primero que vamos a examinar es nuestro archivo build.gradle.kts. Puedes encontrarlo en la raíz de la carpeta del proyecto. Este es el archivo donde podemos gestionar nuestras dependencias y, dado que utilizamos Spring Initializer para la configuración inicial, podemos ver que la herramienta creó este archivo con nuestra configuración y las dependencias seleccionadas.

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
	id("org.springframework.boot") version "2.5.7"
	id("io.spring.dependency-management") version "1.0.11.RELEASE"
	kotlin("jvm") version "1.5.31"
	kotlin("plugin.spring") version "1.5.31"
	kotlin("plugin.jpa") version "1.5.31"
}

group = "com.backendhorizon"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
	developmentOnly("org.springframework.boot:spring-boot-devtools")
	runtimeOnly("com.h2database:h2")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
	kotlinOptions {
		freeCompilerArgs = listOf("-Xjsr305=strict")
		jvmTarget = "11"
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

Dado que elegimos usar Kotlin, Spring Initializer ya creó este archivo de configuración en Kotlin. Para las personas que no están familiarizadas con Gradle, señalo esto porque también es posible escribir tus archivos Gradle con Groovy. Ya que estamos usando Kotlin recomiendo encarecidamente que también lo uses con Gradle, ya que tiene algunas ventajas sobre Groovy, como la autocompletación, por ejemplo.

Gradle Plugins

Echemos un vistazo al bloque de plugins. Gradle en sí no hace mucho por defecto, pero nos ofrece a los desarrolladores opciones para extender y agregar nuevas funcionalidades a nuestras compilaciones. Un gran ejemplo de esto es el bloque de “plugins” donde tenemos plugins que configuran y dejan nuestro proyecto listo para usar muy fácilmente.

Podemos ver que tenemos algunos plugins y la mayoría de ellos son para hacer una configuración inicial para Spring y Kotlin. Echemos un vistazo a la página del plugin Spring Boot y veamos cómo se describe el plugin:

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that can you can “just run”. We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.

En otras palabras, una configuración fácil para empezar a trabajar en tu proyecto de inmediato. Todos los demás plugins hacen algo similar, preparan y ayudan a gestionar las dependencias de nuestro proyecto para que se configuren e integren de la mejor manera.

Debajo de los plugins, tenemos los metadatos que elegimos y la versión de Java.

Maven Central es el repositorio desde donde estamos descargando nuestras dependencias y debajo del bloque de dependencies, están listadas todas las bibliotecas que tenemos en el proyecto. Más adelante volveremos a este archivo e incluiremos algunas dependencias adicionales.

Hay otro archivo importante que vale la pena echarle un vistazo rápido y es nuestro archivo Application.

Application file

Vamos a abrir nuestro archivo de Application. En el proyecto en GitHub, puedes encontrar este archivo titulado como BankApplication pero esto cambiará dependiendo de lo que elijas en tus metadatos. Aquí encontramos la función más famosa de todas, la función main, donde empieza la aplicación.

package com.backendhorizon.bank

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class BankApplication

fun main(args: Array<String>) {
	runApplication<BankApplication>(*args)
}

Podemos ver que en el método principal tenemos esta función runApplication indicando ejecutar con la clase BankApplication y en esta clase, solo tenemos una anotación, @SpringBootApplication. Esta única anotación hace mucho por nosotros.

Echemos un vistazo rápido a cómo lo describe la documentación:

Indica una clase de configuración que declara uno o más métodos @Bean y también desencadena la autoconfiguración y el escaneo de componentes. Esta es una anotación de conveniencia que es equivalente a declarar @Configuration, @EnableAutoConfiguration y @ComponentScan.

En el próximo capítulo, crearemos nuestro primer Controller y algo que notarás es que no crearemos una instancia del controller. Esto se debe a que la anotación @SpringBootApplication está compuesta por otras 3 anotaciones y una de ellas es @ComponentScan.

Como su nombre ya sugiere, @ComponentScan buscará/escandará nuestro proyecto en busca de componentes y cuando lo encuentre, Spring creará y gestionará esta instancia por nosotros. Algo interesante a mencionar es que para que un componente sea autodetectado, debe estar al mismo nivel de paquete o por debajo de la clase de la aplicación.

Los componentes son clases anotadas con anotaciones especiales que califican estas clases para ser autodetectadas. Hay unas cuantas anotaciones diferentes que se pueden utilizar para diferentes propósitos como @Component, @Controller, @RestController, @Repository y @Service.

La @EnableAutoConfiguration hará algo similar para las bibliotecas en el proyecto. Buscará en nuestro classpath, verificará las bibliotecas que estamos usando y cargará una configuración inicial para nosotros. Veremos esto con nuestra base de datos. Sigue leyendo. 😉

Primera API

Vamos a crear nuestro primer controlador de API. Crea un paquete llamado controller y en él, una clase llamada TransferController. Crea 2 métodos, newTransfer y getAllTransfers como en el ejemplo de abajo.

package com.backendhorizon.bank.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/transfer")
class TransferController {

    @PostMapping("/new")
    fun newTransfer() {
        System.out.println("Saved")
    }

    @GetMapping("/all")
    fun getAllTransfers(): String {
        return "Hello"
    }

}

Entendamos lo que está sucediendo aquí. Encima de nuestra clase TransferController, usamos 2 anotaciones, @RestController y @RequestMapping:

  • @RestController: Con esta anotación indicamos a Spring Boot que esta clase es un manejador web/controlador web. Recibirá solicitudes y devolverá respuestas a los llamadores. Dado que es un tipo de “controlador” este componente (ten en cuenta este término) también será gestionado por Spring Boot.
  • @RequestMapping: Esto indica cuál será la firma que tendrá este controlador web en la dirección de la solicitud, después de la dirección del host. Por ejemplo, nuestros 2 métodos tendrán las siguientes direcciones: localhost:8080/transfer/new y localhost:8080/transfer/all.

Siguiendo los métodos, son relativamente simples:

  • newTransfer registra un mensaje de “Saved” en la consola y su anotación indica que esta será una solicitud “Post”, @PostMapping y, al igual que el controlador, indica cuál es la firma para este método, localhost:8080/payment**/new**.
  • getAllTransfer devuelve un mensaje de “Hello” al llamador y, similar al método anterior, indica el tipo de solicitud, en este caso, una solicitud Get, @GetMapping, seguida de la firma, localhost:8080/payment/all.
  • Get y Post son parte de la especificación REST, si no estás familiarizado con ellos, deja un comentario y crearé un nuevo artículo explicándolos más a fondo.

Running & Testing

Ahora tenemos todo lo que necesitamos para hacer nuestra primera prueba. Ve a la función principal en el archivo de la aplicación y ejecuta tu aplicación. Deberías empezar a ver algunos registros y Tomcat nuestro WebServer se iniciará y alojará la aplicación sin ningún problema. Fíjate en los registros que Tomcat te dirá en qué puerto está funcionando tu aplicación.

Tomcat logs

Ahora necesitamos probar estos endpoints. La mejor manera es usar una herramienta hecha para eso. Personalmente recomiendo Postman. Tiene soporte completo para APIs REST, lo que hace posible probar todos los diferentes tipos de endpoints REST, configurando cuerpos JSON, cabeceras, tokens de autorización, etc. Postman también tiene soporte para GraphQL en caso de que te guste. 😂

Abre una nueva pestaña en Postman, selecciona GET y escribe la dirección del endpoint getAllTransfers, como en la imagen a continuación:

Postman screen

Como puedes ver, recibimos un mensaje de “Hello” en respuesta. ¡Nuestra API está funcionando! Haz lo mismo para newTransfer, cambia la URL y el tipo a POST. Verás que la API también está funcionando y el mensaje “Saved” se imprime en la consola de IntelliJ Idea.

Fíjate que no creamos ninguna instancia de TransferController, solo la anotación @RestController indicó a Spring que nuestro controlador necesitaba ser creado. ¡Funciona de maravilla, ¿verdad?!

Modelos de Datos

Empecemos a preparar nuestra aplicación para recibir y guardar datos. Dentro del paquete del controlador, crea uno nuevo llamado model. Crea dos nuevas clases, TransactionModel y OverviewTransactionModel.

En la clase TransactionModel vamos a crear algunos atributos. Así que imaginemos que para hacer una transferencia a otra persona necesitamos saber cuál es el identificador de cuenta, que puede ser un número o una cadena, dependiendo del país en el que vivas. También necesitamos el valor y una descripción que puede ser opcional.

package com.backendhorizon.bank.controller.model

class TransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String = "",
)

Cuando obtenemos la lista de todas las transacciones, nos gustaría ver un poco más de datos. Además de todos los datos anteriores, tendremos dos campos adicionales, la fecha de la transacción y el identificador de la transacción (id).

package com.backendhorizon.bank.controller.model

import java.util.*

class OverviewTransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String,
    val date: Date,
    val id: String
)

Actualiza tu TransferController como el código a continuación con los nuevos modelos que creamos. Puedes notar que en el método newTransfer tenemos una nueva anotación @RequestBody. Esto indica que el TransactionModel es parte de esta solicitud y deserializará el JSON automáticamente en nuestro modelo.

package com.backendhorizon.bank.controller

import com.backendhorizon.bank.controller.model.OverviewTransactionModel
import com.backendhorizon.bank.controller.model.TransactionModel
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/transfer")
class TransferController {

    @PostMapping("/new")
    fun newTransfer(@RequestBody transactionModel: TransactionModel) {
        // TODO: SAVE THE DATA
    }

    @GetMapping("/all")
    fun getAllTransfer(): List<OverviewTransactionModel> {
        // TODO: FETCH THE DATA
        return listOf()
    }

}

Base de Datos

Ahora que estamos recibiendo datos a través de nuestra API, necesitamos almacenarlos en una base de datos. Vamos a usar una base de datos en memoria llamada H2 que ya hemos incluido en nuestro archivo build.gradle.kts.

Crea un nuevo paquete al mismo nivel que el controlador llamado repository. Dentro de este paquete, una interfaz llamada TransferRepository. También creemos otro paquete llamado model dentro de repository y en él una clase llamada TransactionDBModel. Al final, deberías tener algo como esto:

Estructura del proyecto

Primero, trabajemos en los atributos del modelo de la BD. Similar a TransactionModel tendremos el identificador de cuenta, valor y descripción. Para guardar en la base de datos, necesitamos tener un identificador único y este identificador será generado automáticamente para nosotros. También vamos a usar la fecha actual como la fecha de la transacción.

package com.backendhorizon.bank.repository.model

import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity
class TransactionDBModel(
    val accountIdentifier: String,
    val value: Double,
    val description: String = "",
) {
    @Id
    @GeneratedValue
    var id: UUID? = null
        private set

    val date: Date = Date()
}

Por constructor, vamos a recibir los atributos que recibimos a través de nuestra API y en el cuerpo de la clase, tendremos los dos atributos adicionales (id y fecha). Ya podríamos inicializar el id con un UUID aleatorio, pero dejaremos que JPA se encargue de eso. Hacemos el set privado para que no podamos sobrescribir este valor, solo leerlo. La fecha siempre será la actual, así que la inicializamos con la fecha actual.

En esta clase tenemos tres nuevas anotaciones:

  • @Entity: Esto indica que esta clase será utilizada por JPA (Java Persistence API) para guardar los datos en una base de datos.
  • @Id: Indica que el campo anotado será la clave primaria en la base de datos para esta entidad.
  • @GeneratedValue: el nombre es autoexplicativo. Indica que esta clave primaria debe auto-generar su propio valor.

Ahora que tenemos nuestro modelo, crearemos nuestra interfaz que realizará operaciones CRUD (Crear, Leer, Actualizar y Eliminar). La forma en que vamos a hacer esto es extendiendo nuestro TransferRepository con CrudRepository. Es asombroso lo simple y poderoso que es esto.

package com.backendhorizon.bank.repository

import com.backendhorizon.bank.repository.model.TransactionDBModel
import org.springframework.data.repository.CrudRepository
import java.util.*

interface TransferRepository: CrudRepository<TransactionDBModel, UUID>

Si abres la interfaz CrudRepository, es fácil ver lo que está sucediendo. Esta interfaz tiene todas las funcionalidades genéricas para CRUD y todo lo que necesitamos es indicar qué tipo es nuestro modelo y la clave primaria, en nuestro caso TransactionDBModel y UUID.

Solo indicando los tipos de modelo y clave primaria, Spring descubrirá que estamos usando esta interfaz y inyectará una implementación predeterminada para esta interfaz (y los tipos que proporcionamos). Puedes echar un vistazo a la implementación predeterminada, es una clase llamada SimpleJpaRepository.

Por supuesto, no estamos limitados solo a estas funciones. También es posible crear operaciones de base de datos más complejas, pero para este artículo, trabajaremos solo con esta implementación predeterminada.

De la API a la base de datos y viceversa

Ahora que nuestra capa de persistencia está terminada, podemos guardar los datos que estamos recibiendo de la API. Lo único que necesitamos hacer es una pequeña conversión entre nuestros modelos, ya que el modelo que recibimos en el endpoint es diferente del que usamos en la base de datos. Vamos a crear dos funciones de extensión para convertir estos modelos, convertToDBModel y convertToOverviewTransactionModel.

Primero en TransactionModel, convierte el modelo del endpoint en uno de base de datos, TransactionDBModel.

package com.backendhorizon.bank.controller.model

import com.backendhorizon.bank.repository.model.TransactionDBModel

class TransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String = "",
)

fun TransactionModel.convertToDBModel() = TransactionDBModel(
    accountIdentifier = this.targetAccount,
    value = this.value,
    description = this.description
)

Y para la descripción general, algo similar. Pero esta vez el modelo de base de datos TransactionDBModel en un endpoint, OverviewTransactionModel.

package com.backendhorizon.bank.controller.model

import com.backendhorizon.bank.repository.model.TransactionDBModel
import java.util.*

class OverviewTransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String,
    val date: Date,
    val id: String
)

fun TransactionDBModel.convertToOverviewTransactionModel() = OverviewTransactionModel(
    targetAccount = this.accountIdentifier,
    value = this.value,
    description = this.description,
    date = this.date,
    id = this.id.toString()
)

OK, ahora podemos convertir los datos entre nuestros endpoints y la base de datos. Actualicemos nuestro controlador para hacer estas llamadas.

En el constructor, incluiremos nuestra clase TransferRepository. Recuerda, Spring es inteligente, cuando Spring ve que tenemos esta dependencia en nuestro controlador, Spring inyectará la implementación creada para TransactionRepository (basada en SimpleJpaRepository).

package com.backendhorizon.bank.controller

import com.backendhorizon.bank.controller.model.TransactionModel
import com.backendhorizon.bank.controller.model.OverviewTransactionModel
import com.backendhorizon.bank.controller.model.convertToDBModel
import com.backendhorizon.bank.controller.model.convertToOverviewTransactionModel
import com.backendhorizon.bank.repository.TransferRepository
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/transfer")
class TransferController(val repository: TransferRepository) {

    @PostMapping("/new")
    fun newTransfer(@RequestBody transactionModel: TransactionModel) {
        repository.save(transactionModel.convertToDBModel())
    }

    @GetMapping("/all")
    fun getAllTransfers(): List<OverviewTransactionModel> {
        return repository.findAll().map { it.convertToOverviewTransactionModel() }
    }

}

Puedes ver en el ejemplo anterior que nuestra clase de repositorio está usando los métodos genéricos de CrudRepository para guardar y encontrar todos las transacciones en la base de datos. También hacemos uso de los métodos de extensión que creamos para convertir el modelo al más apropiado.

En este punto, nuestros endpoints están todos listos. Recompila y ejecuta la aplicación. Vamos a crear una nueva solicitud de transferencia. Recuerda que ahora necesitamos incluir todos los datos necesarios en Postman para que nuestro endpoint pueda trabajar correctamente. Incluye un cuerpo JSON como en la imagen a continuación y envíalo.

Pantalla de Postman

Después de enviar, todo debería funcionar bien. Deberías recibir un estado 200, lo que significa que la solicitud fue exitosa. Ahora, si vuelves a buscar todos los datos, verás los datos de la transferencia enviada, incluyendo la fecha y el id generado.

Pantalla de Postman

¡Nuestra API está funcionando 🎉 pero aún no hemos terminado!

Database Console

Hay una forma muy agradable que H2 Database nos ofrece para verificar los datos guardados y esa es la “H2 Console”. Es un administrador web donde puedes verificar los datos de la base de datos y ejecutar consultas.

Primero, para habilitar esta consola necesitas agregar el siguiente código en tu archivo application.properties dentro de la carpeta de recursos.

# Enabling H2 Console
spring.h2.console.enabled=true

Después de agregar, ejecuta tu aplicación nuevamente e ingresa en tu navegador la URL http://localhost:8080/h2-console. Verás una ventana de inicio de sesión para conectarte a tu base de datos. Lo único que necesitas cambiar es la JDBC Url. Busca en los logs de tu consola y busca “H2 console available at …” como en la imagen a continuación.

Copia la URL de la base de datos y presiona conectar. Deberías ver la consola. Ahora puedes jugar con tu base de datos, ejecutar consultas SQL y ver los datos que se están guardando desde tu API.

Console logs

¿Ya hemos terminado, verdad? ¡INCORRECTO! Somos buenos desarrolladores y los buenos desarrolladores escriben pruebas!

Unit tests

Antes de escribir nuestra primera prueba, añadamos una nueva dependencia en el bloque de dependencias dentro de build.gradle.kts. Con esta dependencia en lugar de Mockito, vamos a usar Mockk para crear instancias simuladas.

// ... other dependencies already included
testImplementation("com.ninja-squad:springmockk:3.0.1")

Esta biblioteca nos ayuda a soportar mejor Mockk en el contexto de Spring. Puedes leer más sobre ello aquí.

Crea una nueva clase llamada PaymentsControllerTest dentro de la carpeta de pruebas. Tómate un momento para mirar las pruebas a continuación y entenderemos qué está pasando.

package com.backendhorizon.bank.controller

import com.fasterxml.jackson.databind.ObjectMapper
import com.backendhorizon.bank.controller.model.TransactionModel
import com.backendhorizon.bank.repository.TransferRepository
import com.backendhorizon.bank.repository.model.TransactionDBModel
import com.ninjasquad.springmockk.MockkBean
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import java.util.*


@WebMvcTest
class PaymentsControllerTest(@Autowired val mockMvc: MockMvc) {

    @MockkBean
    private lateinit var paymentsRepository: TransferRepository


    @Test
    fun `should submit transaction with success`() {
        val transfer = TransactionModel(
            value = 1.50,
            description = "store 1",
            targetAccount = "NL76ABNA2376059879"
        )

        every { paymentsRepository.save(any()) } returns mockk()

        mockMvc.perform(post("/transfer/new")
            .content(ObjectMapper().writeValueAsString(transfer))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isOk)
    }

    @Test
    fun `should get transaction with success`() {
        val mockTransaction = mockk<TransactionDBModel>().apply {
            every { value } returns 1.50
            every { description } returns "store 1"
            every { accountIdentifier } returns "NL47INGB8845464385"
            every { date } returns Date()
            every { id } returns UUID.randomUUID()
        }

        every { paymentsRepository.findAll() } returns listOf(mockTransaction)

        mockMvc.perform(get("/transfer/all").accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isOk)
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(jsonPath("\$.[0].description").value("store 1"))
    }

    @Test
    fun `should return a bad request error if the request there's no body`() {
        mockMvc.perform(post("/transfer/new")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().is4xxClientError)
    }

}

La idea general de lo que las pruebas están haciendo es fácil de entender gracias al marco de pruebas de Spring, que es muy expresivo. Echemos un vistazo a los detalles técnicos:

  • @WebMvcTest: Esta anotación indica que estas pruebas están probando un componente MVC, en este caso, un controlador REST.
  • MockMVC: Este es el punto de entrada que nos permitirá comunicarnos con nuestro controlador durante las pruebas, haciendo solicitudes y verificando los resultados y estados.
  • @MockkBean: Estamos simulando una clase TransferRepository falsa para simular diferentes escenarios. Simulamos estos escenarios siempre antes de realizar una acción con MockMvc, simulando el resultado de los métodos findAll y save.

Después de configurar nuestros mocks, realizamos una solicitud al controlador usando MockMvc. Todas las pruebas siguen una estructura similar:

  • Le decimos a MockMvc que realice una solicitud y qué tipo es (post o get).
  • Si tenemos un cuerpo en la solicitud, transformamos el objeto en un JSON.
  • El tipo de contenido de la solicitud, APPLICATION_JSON.
  • Imprimimos en la consola los detalles y resultados de la solicitud.
  • Y finalmente, declaramos lo que esperamos de esta solicitud. Puede ser un código de estado verificando si la solicitud tuvo éxito o una respuesta JSON (y el contenido).

Y finalmente hemos terminado. 🎉🎉🎉

Puedes encontrar el proyecto completo en GitHub.


Preguntas o sugerencias, por favor deja un comentario, estamos aquí para aprender juntos. 🤠

Gracias por leer.

comments powered by Disqus

Publicaciones relacionadas