Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 20, 2021 06:13 am GMT

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")}

5. Test source root adicional

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")

6. Aadimos una base de datos

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 el dependsOn en el container de la app para que desde ste ltimo el hostname mypostgres 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 entorno DB_HOSTNAME y el valor mypostgres. En el entorno real db.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")    }}

9. Tests en verde!

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

Alt Text


Original Link: https://dev.to/adevintaspain/testeando-una-spring-boot-app-dockerizada-3k5

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To