JUnit con bases de datos
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.
Contenido
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() { ... }
}