JUnit con bases de datos

De Dos Ideas.
Saltar a: navegación, buscar

Es muy común tener que integrar JUnit para ejecutar tests contra una base de datos. Vamos a ver algunas facilidades que nos provee JUnit y Spring Framework para hacer más facil la ejecución de tests contra datos siempre limpios.

embedded-database de Spring

Spring Framework contiene una utilidad para levantar bases de datos en memoria al momento que se inicializa el factory. De esta forma, podemos inicializar una base y a la vez decirle que ejecute varios scripts, para dejarla preparada y lista para usar.

Un archivo de configuración de ejemplo: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"

      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jdbc="http://www.springframework.org/schema/jdbc"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd">
   <jdbc:embedded-database id="dataSource" type="HSQL" />
   <jdbc:initialize-database data-source="dataSource" ignore-failures="NONE">
       <jdbc:script location="classpath:tablas.sql" />
       <jdbc:script location="classpath:datos.sql "/>
   </jdbc:initialize-database>

</beans>

Esta configuración crea un bean llamada "dataSource" que es un DataSource común, listo para inyectar en nuestra aplicación (y reemplazar así un DataSource real). Este DataSource apunta a una base en memoria HSQLDB, y ejecutarán 2 scripts al momento de inicialización: tablas.sql y datos.sql

Limpieza del contexto de Spring en los tests

Es posible que algún método de test "ensucie" (cambie) el valor de algún bean del factory, lo cual ocasione problemas en tests posteriores. Podemos usar la anotación @DirtiesContext para indicarle a Spring que vuelva a crear un nuevo factory luego de la ejecución del método anotado con esta anotación.

Por ejemplo:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {

   "classpath:datasource-test.xml",
   "classpath:applicationContext.xml"

}) public class MiTest {

   @Test
   public void fooTest() { ... }
   @Test
   @DirtiesContext
   public void barTest() { ... }

}

En este ejemplo, al terminar la ejecución del método barTest() se volverá a crear el factory de Spring (y por lo tanto, se creará una nueva base en memoria). De esta manera el resto de los métodos contarán con un entorno limpio.

Limpieza de la base de datos con Rules de JUnit

En la mayoría de las ocasiones, los métodos @Test no ensucian el contexto de beans de Spring, sino que ensucian los datos de la base de datos (realizando inserts, updates, deletes sobre los mismos). Los métodos posteriores de tests podrían verse afectados por no contar con los datos tal cual se los esperaba.

Una solución es anotar a estos tests que ensucian la base de datos con @DirtiesContext. Esto forzará la creación de un nuevo contexto de Spring, y se recreará la correspondiente base de datos en memoria. Sin embargo, esta solución es relativamente costosa por la continua destrucción/construcción del factory de Spring.

Una solución alternativa será usar un @DirtiesDatabase que sólo recree la base de datos en memoria... sin embargo, Spring no tiene esta utilidad. Por suerte, es muy simple agregarla.

@DirtiesDatabase

Crearemos una anotación de método que nos permita indicar que un método de @Test ensucia la base de datos, y luego usaremos una Regla de JUnit para leer esta anotación y hacer la limpiaza necesaria.

La anotación

La anotación es común: @Retention(RetentionPolicy.RUNTIME) public @interface DirtiesDatabase { }

DatabaseRebuilder para reconstruir la base de datos

Primero vamos a crear una interfaz para una clase que se encargue de reconstruir la base de datos: public interface DatabaseRebuilder {

   void rebuildDatabase();

}

Una posible implementación de esta interfaz podría ser invocar a los scripts de inicialización de la base en memoria.

La regla de JUnit

Luego, crearemos una regla de JUnit que buscará esta anotación, y si la encuentra, reconstruye la base de datos. Esta regla usa una instancia de DatabaseRebuilder para hacer la reconstrucción de la base de datos: public class DirtiesDatabaseRule implements TestRule {

   private DatabaseRebuilder rebuilder;
   public DirtiesDatabaseRule(DatabaseRebuilder rebuilder) {
       this.rebuilder = rebuilder;
   }
   @Override
   public Statement apply(final Statement base, final Description description) {
       return new Statement() {
           @Override
           public void evaluate() throws Throwable {
               try {
                   base.evaluate();
               } finally {
                   DirtiesDatabase dirtiesDatabase = description.getAnnotation(DirtiesDatabase.class);
                   if (dirtiesDatabase != null) {
                       rebuilder.rebuildDatabase();
                   }
               }
           }
       };
   }

}

Usando @DirtiesDatabase

Listo! Nos queda agregar la regla a la clase de test, y la anotación correspondiente.

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {

   "classpath:datasource-test.xml",
   "classpath:applicationContext.xml"

}) public class MiTest {

   @Rule
   public DirtiesDatabaseRule dirtiesDatabaseRule = new DirtiesDatabaseRule(new DatabaseRebuilderImpl());
   @Test
   public void fooTest() { ... }
   @Test
   @DirtiesDatabase
   public void barTest() { ... }

}