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
Docker 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
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.
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.