Blog>

Carlos Múgica
13 Dic 2022

Testea las consultas en Base de Datos 

Tiempo de lectura 5 minutos
  • buenas prácticas
  • clean code
  • h2
  • testing
  • unit test

Sumario Aprende a testear las queries de tu sistema montando una base de datos en memoria con H2. Usa tu propio esquema de bbdd y tus datos de prueba. y al final ejecuta tus consultas como si fuesen el sut de tus test unitarios.

Llegará el día en que la lógica de negocio no se encuentre en la base de datos, en que hibernate nos abstraiga de preocuparnos por la queries, pero hoy no es ese día, en este día nuestras queries son complicadas, son importantes, tienen varios niveles de profundidad y lo que es peor, cada dos por tres alguien está metiendo un campo nuevo y rompiéndolas.

¿Y cuál es la mejor solución a estos problemas?

Refactoriza todo tu sistema y traslada la lógica de negocio a tus casos de uso.

¿Y cuál es la segunda mejor solución a estos problemas?

Métele tests. Es más, la forma más segura de refactorizar todo tu sistema va a pasar por meterle tests.

Hemos montado un ejemplo muy sencillito (https://github.com/carlos-devanddel/DBUnitTest) para mostrar cómo llevar a cabo la configuración del entorno para poder llevar a cabo los tests sobre las queries.

Como decíamos, es muy sencillo, se compone un DAO muy simple, que recupera un objeto de base de datos por ID.

@Repository
public class TheDAO {

   @Autowired
   private JdbcTemplate jdbcTemlate;

   public TheModel getDataById(Integer id) throws NotFoundException {
      try {
         return jdbcTemlate.queryForObject("SELECT * FROM MODELS WHERE id = " + id,(rs, rowNum) ->
             new TheModel(rs.getInt("ID"), rs.getString("DESCRIPTION"))
         );
      }catch (EmptyResultDataAccessException e){throw new NotFoundException();}
   }
}

El modelo como podemos ver es un POJO que se compone de un ID y una descripción.

Dependencias que necesitamos

Como dijimos al principio, vamos a utilizar la BBDD de H2, por lo que vamos a necesitar dicha dependencia

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

Para incluir las anotaciones de JPA que nos van a ayudar a crear el esquema inicial de la base de datos también necesitaremos la siguiente dependencia.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Montar el esquema inicial

Una vez hemos incluido las dependencias necesarias, necesitamos decirle a H2 cuál va a ser el esquema de la BBDD. Para ello vamos a aprovechar las anotaciones de JPA y el hecho de que nuestro modelo es una representación directa de la tabla de base de datos.

@Entity
@Table(name = "MODELS")
public class TheModel {

   @Id
   @Column(name = "ID")
   private Integer id;
   @Column(name = "DESCRIPTION")
   private String description;

De esta forma, anotamos la clase como @Entity, le indicamos el nombre de la tabla en @Table y los nombres de cada columna con @Column. Y con esto ya H2 va a montar una base de datos con una tabla MODELS con dos campos, uno ID y uno DESCRIPTION. Lo siguiente, testear.

Testeando

Estos tests van a estar en la frontera entre unitarios y de integración. Según mi punto de vista son unitarios, porque lo que busco es probar que la query se comporta como quiero, pero se podrían considerar de integración porque se pasa la frontera del código a la bbdd.

Sea como sea, para poder ejecutarlos, lo primero vamos a necesitar que Spring monte un contexto para los tests, por lo que necesitaremos, en la misma carpeta en la que están los tests, una clase de configuración con la anotación de @SpringBootApplication y la anotación @EntityScan en la que le indicamos la ruta al paquete en el que están nuestros modelos con las anotaciones para crear el esquema. 

@SpringBootApplication
@EntityScan("dyd.pocs.dbunittest.models")
public class DBTestConfig {}

Una vez listo esto podemos ponernos a nuestros tests, añadiendo un @SpringBootTest e inyectando el DAO desde el que vamos a llamar a nuestras queries. 

@SpringBootTest
class TheDAOTest {

   @Autowired
   private TheDAO sut;

   @Test
   @Sql(statements = "INSERT INTO MODELS (ID, DESCRIPTION) VALUES(1, 'Descripcion')")
   void getDataById_should_returnModel_when_Exists() throws Exception {
      Integer inputId = 1; 

      TheModel result = sut.getDataById(inputId);

      Assertions.assertNotNull( result);
      Assertions.assertEquals(1, result.getId());
      Assertions.assertEquals("Descripcion", result.getDescription());
   }

   @Test
   void getDataById_should_throw_NotFoundException_when_modelDoesntExists(){

      Assertions.assertThrows(NotFoundException.class, () -> sut.getDataById(2));
   }
}

Si descomponemos los test en sus tres partes básicas:

Given: preparación de los datos

Aquí está lo único especial que habría que hacer en estos tests, como podemos ver al inicio del primer test tenemos un @Sql, esto es para insertar los datos que esperamos sacar con nuestra consulta.

When: ejecución del sut

Hacemos la llamada al dao con los datos de entrada y recogemos la salida en el objeto result.

Then: comprobación de los resultados

Hacemos las aserciones que queramos sobre el resultado de la ejecución

Pero… es que mis modelos no coinciden con la BD.

Ya sea porque tus modelos no coinciden con la bbdd y no puedes colocar las anotaciones para que te cree el esquema, o que el cliente en el que estás tenga un miedo irracional a la dependencia de JPA, tienes la alternativa de inicializar tu bbdd H2 con un fichero ddl con los CREATE TABLE que necesites para representar tu esquema.

En este caso la configuración sería más sencilla, no necesitaríamos el fichero de configuración con la anotación @EntityScan, ni la dependencia de JPA, ni las anotaciones en los modelos. En su lugar necesitamos el esquema de inicialización de la bbdd en el fichero test/resources/schema.sql

CREATE TABLE MODELS (
    ID INTEGER NOT NULL PRIMARY KEY,
    DESCRIPTION VARCHAR(200)
);

En el repositorio está disponible un ejemplo con esta configuración en la rama initial_schema_ddl.

... yo en realidad lo que tengo es un DB2 y uso cosas del dialecto como LIST_AGG que no funcionan en H2

En este caso tienes como alternativa TestContainers, que con Docker te podría levantar una instancia de tu BD concreta en lugar del H2. El problema de esto es que es un proceso mucho más lento y con una configuración distinta, por lo que lo postergamos para un próximo artículo.

Autor

Carlos Múgica
Carlos Múgica

Desarrollador en dev&del

Capitán en Hello, World!

Especializado en desarrollo JAVA.

Carlos es un apasionado de la calidad del Software, pero lo único que le gusta más que meter test son las sentadillas.

¿Estás interesado?

Déjanos tus datos y contactaremos contigo lo antes posible