Turorial: Pruebas de Integración de Spring Boot con plugin de Docker para Maven

En este tutorial voy a explicar como crear pruebas de integración automáticas en un proyecto de Spring Boot que tiene como dependencia una base de datos Mysql, utilizando para ello la herramienta Docker.

Utilizando Docker podremos ejecutar pruebas de integración utilizando una base de datos real contenida en un contenedor virtual, ahorrándonos tener que instalar una base de datos en nuestro equipo, haciendo la preparación del entorno desarrollo mucho más sencilla y rápida. También obtendremos unas pruebas de integración más cercanas a nuestro entorno de producción que si utilizamos una base de datos embebida tipo h2 porque utilizaremos la misma versión de base de datos que en producción con la única diferencia que estará contenida en un contenedor virtual de Docker.

Docker

downloadDocker es la tecnología que está en boca de todo el mundo últimamente. Pero… ¿Qué es Docker? Es una tecnología que te permite empaquetar aplicaciones con todas sus dependencias en una unidad llamada imagen, que puede ser desplegada dentro de un contenedor virtual basado en tecnología del kernel de Linux. Cada vez que una imagen es desplegada de nuevo en un contenedor virtual comienza en su estado original lo que convierte a Docker en una herramienta de gran utilidad para todo tipo de pruebas de integración. Docker tiene todas las ventajas que podemos tener con una máquina virtual con la diferencia de que es mucho más rápida (el primer despliegue requiere construir o descargar la nueva imagen pero después cada despliegue será mucho más rápido) y su gestión es mucho más sencilla. Docker funciona bajo una licencia de software libre y tiene una comunidad muy activa. Personalmente creo que Docker tiene mucho potencial y va tener múltiples aplicaciones en el futuro, algunas que todavía ni nos imaginamos.

Uso

Desplegar un contenedor virtual con una imagen que contiene Mysql, con una base de datos llamada «db» y con clave de root con valor «root» es tan sencillo como ejecutar el siguiente comando:

docker run -p 3307:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=db -d mysql:latest
Con «docker run» se despliega un contenedor con la imagen que se le pasa como último parámetro en este caso mysql:lastest. Esta es una imagen oficial de Mysql que se encuentra en su página de docker hub. Docker hub es un repositorio público de imágenes de Docker. Con -p se define un mapeo entre puertos del contenedor y de la máquina local, en este caso el puerto por defecto de Mysql 3306 se enlaza con el puerto 3307, dándonos acceso al Mysql del container desde http://localhost:3307. Con -e se definen variables de entorno. Esta imagen está preparada para crear una base de datos con el nombre definido en la variable de entorno MYSQL_DATABASE y con la clave definida en la variable de entorno MYSQL_ROOT_PASSWORD.

Plugin de Docker para Maven

Para este tutorial voy a desplegar la misma imagen del ejemplo anterior de una manera automática antes de ejecutar los test de integración y a destruir el contenedor después. Durante los últimos meses se han creado varios plugins para hacer esto desde Maven. Aquí vamos a utilizar este porque después de probar varios de ellos, es el que he me ha parecido más flexible y fácil de usar.

Ejemplo: Pruebas de Integración usando una base de datos Mysql en un contenedor de Docker

En este caso utilizaré un ejemplo muy sencillo con una entidad usuario y su repositorio usando JPA y explicaré como configurar Maven para lanzar contenedores Docker haciendo uso del plugin citado en la sección anterior. Podéis encontrar el código de este ejemplo completo en este repositorio de github.

Generar proyecto

Primero generar un proyecto Maven con dependencias de JPA y Mysql utilizando  Spring Initializr.

springIinit

Definir entidad User y su repositorio

package com.example.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private long id;
    private String firstName;
    private String lastName;

    protected User() {}

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}
package com.example.repository;

import com.example.domain.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByFirstNameAndLastName(String firstName, String lastName);
}

Definir prueba de integración para el repositorio

package com.example.repository;

import com.example.Application;
import com.example.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.Assert.assertFalse;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@Transactional
@IntegrationTest
public class UserRepositoryIT {
    @Autowired
    private UserRepository userRepository;

    @Test
    public void contextLoads() {
        userRepository.save(new User("Name", "LastName"));

        assertFalse(userRepository.findByFirstNameAndLastName("Name", "LastName").isEmpty());
    }
}

Configurar fichero de propiedades

spring.datasource.url = jdbc:mysql://localhost:3307/db
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto = update

Configurar docker-maven-plugin para desplegar el contenedor de Docker con una imagen con base de datos Mysql el fichero pom

Para configurar este plugin tenemos que definir dos bloques después de instanciarlo: configuration y executions.

El en el primer bloque se define que imagen se lanza y con que configuración. En este ejemplo obtendremos un efecto similar al que tendríamos ejecutando el comando antes expuesto, ya que he utilizado los mismos parámetros:

docker run -p 3307:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=db -d mysql:latest

La única diferencia es la sección wait donde se definen reglas para aplicar una espera antes de lanzar las pruebas de integración. En este caso la regla define que se espere a que Mysql este disponible en el puerto 3306 del contenedor.

En  el segundo bloque definimos que el contenedor se cree antes de las pruebas de integración y se destruya después de ellas:

<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<images>
<image>
<name>mysql:latest</name>
<run>
<ports>
<port>3307:3306</port>
</ports>
<env>
<MYSQL_ROOT_PASSWORD>root</MYSQL_ROOT_PASSWORD>
<MYSQL_DATABASE>db</MYSQL_DATABASE>
</env>
<wait>
<time>20000</time>
<tcp>
<host>localhost</host>
<ports>
<port>3306</port>
</ports>
</tcp>
</wait>
</run>
</image>
</images>
</configuration>
<executions>
<execution>
<id>start</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>

Finalmente debemos configurar el plugin maven-failsafe-plugin:

<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.17</version>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>

Después de estos pasos podemos verificar que las pruebas de integración se ejecutan correctamente ejecutando mvn verify.

screenshootç