Test de Integración en Spring Boot con Testcontainers

testcontainers en Spring Boot

testcontainers en Spring Boot


Cuando realizamos test integrados en nuestra aplicación Spring Boot con Spring Data, una forma muy común de hacer estas pruebas es haciendo uso de una base de datos embebida y en memoria como H2. Aunque esta es una forma muy válida en ocasiones, vamos a querer probar contra una base de datos lo más real posible, este es el objetivo de este tutorial, demostrar como podemos realizar test de integración en Spring Boot con Testcontainers contra una base de datos PostgreSQL.

Si quieres ver más posibilidades de testing con Spring Boot no te pierdas un artículo anterior sobre Testing en Spring Boot.

¿Qué es Testcontainers?

Testcontainers es un conjunto de librerías escritas y creadas con el objetivo de crear test de integración y end-to-end. Gracias a las ventajas que nos proporciona el uso de Docker, vamos a poder crear infraestructuras con bases de datos y sistemas de mensajería, para integrarlos con Spring Boot y facilitar los test integrados con Bases de Datos o con Kafka por ejemplo.

¿Qué es un Test Integrado?

Como ya indicamos en nuestro artículo sobre testing, los test de integración o Integration Test, son aquella parte del testing de nuestras aplicaciones en donde los módulos son integrados lógicamente y testeados como un grupo. El objetivo de este tipo de test es ver y analizar los posibles defectos y errores en la interacción entre las diferentes capas y ver como se integran entre ellas.

¿Cómo usar Testcontainers con Spring Boot?

Para probar Testconatiners vamos a partir de un ejemplo que realizamos en un artículo anterior. Este ejemplo estará formado por una aplicación Spring Boot, que utiliza Spring Data para conectarse a una Base de Datos PostgreSql.

Para poder hacer uso de Testcontainers lo primero es añadir su dependencia al pom.xml:

Dependencias Maven para Testcontainers

		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>postgresql</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>junit-jupiter</artifactId>
			<scope>test</scope>
		</dependency>

Configuración Testcontainers en Spring Boot por clase

Vamos a hacer uso de PostreSQL a través de contenedores en nuestras clase, para ello primero crearemos un contenedor y estableceremos los parámetros de la conexión:

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class CarRepositoryPropertiesIntegrationTest {

  @Autowired
  private CarRepository carRepository;


  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
    registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
  }

  @Container
  public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11-alpine")
      .withDatabaseName("integration-tests-db")
      .withPassword("inmemory")
      .withUsername("inmemory");


  @Test
  public void save_and_read_book() {
    Car car = new Car();
    car.setDescription("New car");

    carRepository.save(car);

    List<Car> allCars = carRepository.findAll();

    assert !allCars.isEmpty();
    assert allCars.get(0).getDescription().equalsIgnoreCase("New car");
    assert allCars.size() == 1;
  }

}

Como podemos ver en nuestra clase anterior hemos utilizado la anotación de Spring Boot @SpringBootTest para la inicialización del contexto, y tresanotaciones más para poder crear e instanciar un contenedor, en este caso PostrgreSQL. Las dos anotaciones que vamos que utilizamos de testcontainer son:

  • @AutoConfigureTestDatabase anotación para configurar los test con JUnit y le indicamos por parámetro la no configuración de la base de datos.
  • @Testcontainers, con lo que le indicamos a nuestra clase que vamos a hacer uso de Testcontainers y de esta manera podrá instanciar un contenedor.
  • @Container, indicamos mediante una variable static los parámetros de configuración de nuestro contenedor.

Con esta aproximación vamos a crear una instancia de base de datos por cada clase, y una vez todos los test de esa clase terminen se eliminará esa instancia. Esta aproximación, es muy buena, porque permite crear una mayor independencia y aislamiento entre los test, pero crea un código mucho más verboso y con muchas más líneas de código.

Además vamos a tener que añadir una última configuración para que nuestro test funcione correctamente, añadir una propiedad en nuestro application.yml con la url de configuración de TestContainers. Este application.yml deberá estar en la carpeta resources de nuestros test. Tendrás que tener en cuenta si los test los vas a arrancar con algún profile para indicarlo en este fichero:

spring.datasource.url=jdbc:tc:postgresql:11.1:///integration-tests-db

Como consideración a tener en cuenta en la url se añade tc: para hacer referencia a que es la url de Testcontainers, la versión de postgresql y el nombre de la base de datos.

Crear una instancia de Testcontainer para compartir en todos los test

En cuanto a la utilización de Testcontainers, podemos ver dos aproximaciones diferentes, la primera (vista en el punto anterior) crearían una instancia de la base de datos por clase; y la segunda (que es la que veremos a continuación), crearían una única instancia de la base de datos para todos los test la cual compartirían los test.

Para crear esta clase común a todas nuestras clases de test vamos a extender de PostgreSQLContainer. El cual nos facilitará los métodos necesarios para añadir la configuración a nuestro contenedor.

public class CommonPostgresqlContainer extends PostgreSQLContainer<CommonPostgresqlContainer> {

  private static final String VERSION = "postgres:11.1";

  private static CommonPostgresqlContainer container;

  private CommonPostgresqlContainer() {
    super(VERSION);
  }

  public static CommonPostgresqlContainer getInstance() {
    if (container == null) {
      container = new CommonPostgresqlContainer();
    }
    return container;
  }

  @Override
  public void start() {
    super.start();
    System.setProperty("DB_URL", container.getJdbcUrl()); 
    System.setProperty("DB_USERNAME", container.getUsername());
    System.setProperty("DB_PASSWORD", container.getPassword());
  }

  @Override
  public void stop() {
    //do nothing, JVM handles shut down
  }


}

En la nueva clase que se ha creado, se añdirá la configuración necesaria para instanciarse en todas las clases que hagan uso de un test con Base de Datos. Para ello hemos definido dos métodos con un start y un stop en el que añadiremos la configuración de nuestro contenedor.

El método stop(), lo hemos dejado vacío de modo que delegamos en la JVM su eliminación. Por otro lado en el método start(), hemos añadido la configuración a nuestra base de datos. No olvidar que la propiedad spring.datasource.url, debe ir rellena:

spring.datasource.url=jdbc:tc:postgresql:11.1:///integration-tests-db

Para que nuestros todos nuestros test hagan uso del mismo contenedor la inicializaremos de la siguiente manera:

@SpringBootTest
public class CarRepositorySharedIntegrationTest {

  @Autowired
  private CarRepository carRepository;

  public static PostgreSQLContainer postgreSQLContainer = CommonPostgresqlContainer.getInstance();

  @Test
  public void save_and_read_Car() {
    Car car = new Car();
    car.setDescription("New car");

    carRepository.save(car);

    List<Car> allCars = carRepository.findAll();

    assert !allCars.isEmpty();
    assert allCars.get(0).getDescription().equalsIgnoreCase("New car");
    assert allCars.size() == 1;
  }


}

Para que nuestros test puedan compartir una instancia de nuestro contenedor vamos a inicializarlo con una variable static.

Conclusion

Tal y como analizamos en otro artículo sobre testing en el que vimos la importancia que supone realizar test de integración en nuestras aplicaciones Spring Boot. En este artículo sobre Test de Integración en Spring Boot con Testcontainers, nos hemos centrado sobre todo en la parte de test de integración con Base de Datos.

Testcontainer nos va a facilitar la creación de contenedores para realizar de una manera sencilla test de integración. Permitiendo aislar las capas de nuestra aplicación y haciendo test mucho más reales que haciendo uso de, por ejemplo, H2.

Si quieres ver el ejemplo completo puedes descargarlo desde nuestro github aquí.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!


Deja una respuesta

Tu dirección de correo electrónico no será publicada.