An Interest In:
Web News this Week
- March 21, 2024
- March 20, 2024
- March 19, 2024
- March 18, 2024
- March 17, 2024
- March 16, 2024
- March 15, 2024
Testeando una Spring Boot App dockerizada
Nuestro compaero Alex Castells (@alextremp) en su post Integrando Testcontainers en el contexto de Spring en nuestros tests nos explicaba diferentes maneras para usar Testcontainers en un test de Spring Boot.
Con Daniel Dios (@danieldios) queramos llevarlo un paso ms all y usar Testcontainers para testear una Spring Boot Application una vez dockerizada.
Por eso hicimos un live coding que puedes ver en Twitch o Youtube
Salvo algunos sustos conseguimos nuestro objetivo !!
Puedes consultar el cdigo en AdevintaSpain/spring-boot-docker y te resumimos los pasos a continuacin:
1. Crear una Spring Boot Application con un simple RestController
Usamos Spring Initialzr para crear una Spring Boot Application y empezamos con un test del controlador que queremos implementar:
@SpringBootTest(webEnvironment = RANDOM_PORT)class ApplicationTests { @LocalServerPort private var port: Int = 0 @Test internal fun `should say hello`() { val webClient = WebClient.builder() .baseUrl("http://localhost:$port") .build() val actual = webClient .get() .uri("/hello") .exchangeToMono { it.bodyToMono(String::class.java) } .block() assertThat(actual).isEqualTo("hello Dani&Roger!") }}
2. Dockerizar la Spring Boot App
Existen varias alternativas que puedes consultar en Spring Boot with Docker y en ms detalle en Topical Guide on Docker.
Podramos simplemente crear un Dockerfile
y hacer docker build
pero optamos por usar la tarea bootBuildImage
integrada en la Spring Boot gradle plugin.
Por lo que entendemos esta tarea ya construye la docker image con el orden correcto de capas para asegurar que los elementos que cambian menos frecuentemente se incluyan en las primeras capas, minimizando as el tamao y el tiempo de build.
./gradlew bootBuildImage
3. Testear la Spring Boot App dockerizada
Usamos Testcontainers con un generic container y las anotaciones de JUnit5:
class KGenericContainer(imageName: String) : GenericContainer<KGenericContainer>(imageName)@Testcontainersclass ContainerTest { companion object { private const val appPort = 8080 @Container var app: KGenericContainer = KGenericContainer("spring-boot-docker:0.0.1-SNAPSHOT") .withExposedPorts(appPort) } @Test internal fun `should say hello`() { // ... }
4. Ejecutar bootBuildImage antes del test
Para que siempre testeemos la ltima versin, necesitamos generar la docker image antes de ejecutar el test, lo conseguimos con el dependsOn
de gradle:
tasks.withType<Test> { useJUnitPlatform() dependsOn("bootBuildImage")}
Bajo test
deberamos tener nicamente tests unitarios o mximo slice tests de Spring. Para nada tener tests que dependan de docker. Una manera de crear un test source root adicional para nuestros tests con docker es usar la gradle plugin org.unbroken-dome.test-sets:
plugins { id("org.unbroken-dome.test-sets") version "4.0.0"}testSets { create("container-test")}tasks["container-test"].dependsOn("bootBuildImage")
Para complicar un poco nuestra Spring Boot App aadimos una base de datos postgres y hacemos que nuestro HelloController
ejecute una sencilla query. Como siempre empezamos por el test:
@Testcontainersclass ContainerTest { companion object { private const val postgresAlias = "mypostgres" private const val appPort = 8080 private val network: Network = Network.newNetwork() @Container var postgres: KGenericContainer = KGenericContainer("postgres:13") .withNetwork(network) .withNetworkAliases(postgresAlias) .withEnv("POSTGRES_USER", "myuser") .withEnv("POSTGRES_PASSWORD", "mypassword") .withEnv("POSTGRES_DB", "mydb") @Container var app: KGenericContainer = KGenericContainer(System.getProperty("docker.image")) .withNetwork(network) .dependsOn(postgres) .withEnv("DB_HOST", postgresAlias) .withExposedPorts(appPort) } @Test internal fun `should say hello`() { // ... }
Puntos importantes:
- Ambos containers deben compartir la misma network porque queremos que nuestra app tenga conectividad interna con la base de datos.
- Aadimos un network alias
mypostgres
al container de la base de datos y eldependsOn
en el container de la app para que desde ste ltimo el hostnamemypostgres
se resuelva correctamente. - Necesitamos exponer el puerto
8080
del container de la app porque queremos ejecutar una request desde el test, que estar corriendo fuera de docker. - No necesitamos exponer ningn puerto del container de la base de datos.
- Sobreescribimos la propiedad
db.hostname
que usamos en nuestra Spring Boot App con la variable de entornoDB_HOSTNAME
y el valormypostgres
. En el entorno realdb.hostname
tomar el valor correspondiente a la base de datos real.
6. Convertimos el SpringBootTest original en un WebMvcTest
Queremos testear nicamente el HelloController
, sin que requiera conexin con la base de datos.
7. WebMvcTest no sirve necesitamos WebFluxTest!
Unos momentos de pnico hasta que nos damos cuenta que estamos usando WebFlux con lo que el slice test que debemos usar es @WebFluxTest
y no @WebMvcTest
...
8. WebTestClient y Kotlin madre ma!
Pnico total no sabemos como implementar el expectBody
con WebTestClient
y Kotlin, al final lo salvamos con este workaround sacado de internet ... :
@Testinternal fun `should say hello`() { val version = "Dummy 1.0" doReturn(version).`when`(helloRepository).getVersion() webClient .get().uri("/hello") .exchange() .expectStatus().is2xxSuccessful .expectBodyList(String::class.java) .consumeWith<WebTestClient.ListBodySpec<String>> { assertThat(it.responseBody?.get(0) ?: "xx").isEqualTo("Hello $version") }}
Bueno al menos todos los tests estn en verde
Y ya fuera de cmaras, con ms tranquilidad ...
10. As se usa el WebTestClient
Tan fcil como esto, en qu lo nos metimos?
@Testinternal fun `should say hello`() { val version = "Dummy 1.0" doReturn(version).`when`(helloRepository).getVersion() webClient .get().uri("/hello") .exchange() .expectStatus().isOk .expectBody<String>() .isEqualTo("Hello $version")}
11. Versin de la docker image
Para no tener fija la versin de la docker image de la app en el test de container, la pasamos como system property desde gradle:
testSets { val containerTest = create("container-test") containerTest.systemProperty("docker.image", "${project.name}:${project.version}")}
Y la usamos en el test:
@Containervar app: KGenericContainer = KGenericContainer(System.getProperty("docker.image"))
Y eso es todo! Un saludio de parte de @danieldios & @rogervinas
Original Link: https://dev.to/adevintaspain/testeando-una-spring-boot-app-dockerizada-3k5
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To