1. El papel de las pruebas de repositorio
Después de días intensos con @WebMvcTest, surge la sensación: «Bueno, ya hemos probado la API, así que el sistema es fiable». Y entonces el repositorio sonríe silenciosamente en un rincón, como un gato que acaba de tirar una flor pero finge que no ha sido él. La capa de persistencia se rompe a su manera: no como HTTP, no como JSON, y desde luego no como la lógica de negocio pura en pruebas unitarias.
En una prueba web verificamos que el controlador recibió correctamente la solicitud, llamó al servicio y devolvió el estado/JSON correcto. Pero una prueba web normalmente no demuestra que los datos realmente se guardan y se leen como esperamos. Incluso si simuló el servicio con cuidado, aún queda una enorme capa de incertidumbre: ¿el mapeo JPA es correcto? ¿nullable=false realmente funciona? ¿El repositorio realmente encuentra el artículo por slug y no «por inspiración»?
En ContentHub esto es especialmente notable en cosas básicas: Article almacena campos importantes (slug, estado, autor, timestamps), y necesitamos confiar en que la escritura y lectura en la base de datos se comportan de manera estable. Por eso introducimos una capa separada, honesta y enfocada: pruebas de repositorio.
2. La capa de repositorio en ContentHub: no solo CRUD
La capa de repositorio a menudo se subestima porque Spring Data JPA parece magia: escribes una interfaz y «funciona solo». Pero ese «solo» funciona exactamente hasta que cambias el nombre de un campo, añades una restricción, actualizas el mapeo de una relación o haces un método derivado «obvio» que resultó no ser tan obvio. Y entonces el error se manifiesta no como una excepción elegante con una pista, sino como «no se encuentra nada», «falla al guardar», «en producción otro SQL» y «¿por qué pasó la revisión?».
En nuestro proyecto ContentHub, la capa de repositorio es el lugar donde viven interfaces como ArticleRepository, que se encargan de leer y escribir Article (y más adelante, attachments, categorías, etc.). El repositorio es el límite entre el modelo Java y la base de datos. Nos promete: «Tú das la entidad, yo la escribo en las tablas. Tú das el criterio de búsqueda, yo devuelvo lo que hay en la base de datos». Y las pruebas en este nivel son necesarias para verificar precisamente esa promesa.
Para contextualizar, el repositorio suele verse así (muy simplificado, pero correcto en esencia):
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ArticleRepository extends JpaRepository<Article, Long> {
// Buscamos artículo por clave de negocio (slug), no por id técnico
// Spring Data generará la consulta basándose en el nombre del método
Optional<Article> findBySlug(String slug);
}
Observa: aquí no hay HTTP, ni DTO, ni controladores. Es la «pura» frontera de persistencia. Y probarla a través de @WebMvcTest es como verificar cómo funciona un refrigerador abriendo solo la puerta y alegrándote de que la bombilla esté encendida.
3. @DataJpaTest como test slice
En módulos anteriores nos acostumbramos a la idea de que Spring Boot puede levantar rebanadas estrechas del contexto (slices), y esto acelera mucho las pruebas y las hace más honestas. Para los contratos JSON teníamos @JsonTest, para el límite del controlador — @WebMvcTest. Es lógico que para el límite de persistencia también haya una herramienta especializada, y no «levantemos toda la aplicación y de alguna manera probemos el repositorio dentro de @SpringBootTest».
@DataJpaTest es precisamente esa herramienta: un data slice estrecho que levanta solo lo necesario para JPA y los repositorios. Sirve para verificar rápida y predeciblemente cómo se comportan las @Entity, su mapeo y los repositorios de Spring Data. Es decir, no probamos «la aplicación en su conjunto», sino un subsistema específico: la capa de persistencia.
Y esto es un importante cambio psicológico: @DataJpaTest no se trata de «bueno, al menos alguna prueba». Se trata de disciplina: cada capa tiene su zona de evidencia. El data slice demuestra que la capa de almacenamiento de datos está viva, es adecuada y no nos miente (al menos no intencionadamente).
4. Composición del contexto de @DataJpaTest
Cuando colocas @DataJpaTest en una clase de prueba, Spring Boot reúne un contexto pequeño que se enfoca en JPA. Es útil pensar en esto no como una «anotación», sino como un «modo de ejecución del entorno de pruebas». En este modo, obtienes todo lo necesario para trabajar realmente con la base de datos a través de JPA, pero no obtienes lo superfluo que ralentiza el suite y mezcla responsabilidades.
Fijemos esto en una pequeña tabla: a menudo salva de las expectativas más típicas como «¿por qué mi servicio no se inyecta? si es tan bueno»:
| Parte de la aplicación | En @DataJpaTest por defecto | Comentario en lenguaje humano |
|---|---|---|
| @Entity clases (entidades JPA) | Sí | De lo contrario no habría nada que mapear en tablas |
| Repositorios de Spring Data JPA (JpaRepository y similares) | Sí | Es el objeto principal de verificación |
| EntityManager / infraestructura JPA | Sí | Hibernate (u otro proveedor) debe funcionar |
| DataSource | Sí | Se necesita una base de datos, aunque sea de prueba |
| Transacciones para pruebas | Generalmente sí | Por lo general, las pruebas se ejecutan en una transacción (detalles en la siguiente lección) |
| @Controller, @RestController | No | Es la capa web, aquí sobra |
| @Service y lógica de orquestación | No | Es la capa de negocio, se prueba de otra forma |
| Configuración de seguridad / filtros | No | Es una capa de riesgo separada, no mezclamos |
| MockMvc, HTTP, serialización de respuesta | No | Esto fue en @WebMvcTest |
En la práctica, esto significa: @DataJpaTest es una forma muy rápida de decirle a Spring: «Levántame exactamente la cantidad de aplicación necesaria para que pueda verificar los repositorios y el mapeo JPA. Y por favor, no traigas controladores, seguridad y todo el zoológico de configuraciones».
Y otro punto importante: si en el classpath hay un controlador de base de datos embebida (por ejemplo, H2), Spring Boot a menudo puede levantar la base de datos de prueba automáticamente. Esto es conveniente para empezar y para comprobaciones rápidas. Pero no pretendemos que la base de datos embebida sea 100% igual a PostgreSQL; simplemente la usamos como un campo de entrenamiento rápido. Sin adelantarnos: hoy es importante entender el slice en sí y sus límites.
5. Esqueleto mínimo de una prueba data‑slice
La transición de las pruebas web a las pruebas de datos suele sentirse como un cambio de profesión: ayer montabas peticiones HTTP, hoy de repente piensas en entidades y tablas. Es normal. La buena noticia: se puede empezar de forma muy sencilla. @DataJpaTest hace la mayor parte del trabajo pesado por ti, y de ti solo se requiere una estructura de prueba cuidadosa y una comprensión clara de «qué es exactamente lo que estoy verificando».
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
@DataJpaTest // Levantamos solo la infraestructura JPA y los repositorios de Spring Data
class ArticleRepositoryDataJpaTest {
@Autowired
private ArticleRepository articleRepository; // Este es el "objeto de verificación": repositorio real, no mock
}
Y esto no es un «esqueleto por marcar». Ya en esta etapa es importante notar: no levantamos ContentHubApplication, no iniciamos el servidor, no tocamos los controladores. Es una prueba a nivel de persistencia.
Si estás acostumbrado a que en @WebMvcTest sustituías los servicios con @MockitoBean, aquí la idea es diferente: no mockeas el repositorio, verificas el comportamiento real del repositorio. Mockear el repositorio en una prueba de repositorio es como probar una bicicleta dibujándola con un marcador en papel: formalmente las ruedas son redondas, pero no se puede andar.
6. Prueba: guardamos Article
Normalmente, la primera prueba data‑slice uno quiere hacerla «grande e inteligente»: verificar inmediatamente guardado, lectura, búsqueda por slug, actualización, borrado, y que todo esté en un solo método para no perder tiempo. Las pruebas tienen una propiedad molesta: se leen más a menudo de lo que se escriben. Por eso el primer paso correcto es hacer la prueba pequeña y obvia: demostrar que somos capaces de registrar un artículo a través del repositorio.
El escenario más simple es así: creamos un Article válido, lo guardamos, nos aseguramos de que la entidad guardada tiene un id. La aparición del id no es cosmética. Es una comprobación de cordura mínima de que la entidad pasa exitosamente a través de la capa JPA y obtiene una clave primaria. Pero aún no es el caso en el que demostramos explícitamente el round‑trip físico hasta la base de datos: dentro de un mismo contexto de persistencia, JPA mantiene mucho bajo su control. Por ahora, es importante ver un hecho más simple: el data slice está vivo y el repositorio es capaz de guardar una entidad válida. Cuando sea necesario separar estrictamente el estado managed de los cambios realmente escritos en la base de datos, entrarán en juego flush y clear.
Ejemplo (con un pequeño método helper para legibilidad):
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
@Test
void savesArticleInsideDataSlice() {
// Act: guardamos la entidad a través del repositorio real de Spring Data
Article saved = articleRepository.save(validArticle("spring-data-basics"));
// Assert: si apareció el id, significa que el guardado básico a través de la capa JPA funcionó
assertThat(saved.getId()).isNotNull();
}
Aquí validArticle(...) es simplemente un atajo para un artículo mínimo válido de ContentHub. En una clase de prueba real, es más conveniente construir tal fixture mediante métodos helper separados y junto con la categoría, para que todas las pruebas se basen en el mismo modelo de artículo válido.
7. Base de datos embebida en @DataJpaTest: auto‑levantamiento sin pánico
Cuando ejecutas @DataJpaTest por primera vez, suelen darse dos sensaciones típicas. La primera: «Oh, levantó la base de datos solo — soy un genio». La segunda (normalmente a los 30 segundos): «Espera, ¿qué base de datos levantó? No configuré nada». Ambas sensaciones son normales. Spring Boot realmente intenta hacer el data slice lo más conveniente posible: si en el classpath de prueba hay una base de datos embebida, puede usarla automáticamente como base de datos de prueba.
La idea aquí es simple: las pruebas de repositorio deben ser rápidas y locales, de lo contrario dejan de ser pruebas de repositorio y se convierten en una «mini‑integración con todo el mundo». La base de datos embebida ayuda a obtener retroalimentación rápida y detectar problemas básicos de mapeo y métodos de repositorio, sin requerir del estudiante (y del equipo) una infraestructura pesada desde la primera lección del módulo de datos.
Pero es importante mantener la expectativa correcta: la base de datos embebida es un modo de aprendizaje y rápido, no una «verdad absoluta sobre producción». La usamos como un inicio conveniente para entender la mecánica y aprender a escribir pruebas de repositorio cuidadosas. Las diferencias reales entre bases de datos son un tema aparte, y no convertiremos la primera lección del día de datos en una «guerra de dialectos SQL».
8. Límites del data slice
Es muy fácil convertir una prueba data‑slice en una «cosechadora universal»: añadir servicios, ejecutar el workflow, verificar transiciones de estado de negocio, y de paso asegurarse de que el controlador devuelve 200 OK. Técnicamente puedes intentarlo, pero el sentido del enfoque slice será destruido casi igual que el sentido de una dieta se destruye con la frase «bueno, es un pedacito de pastel». El data‑slice existe precisamente para no mezclar diferentes zonas de responsabilidad.
En @DataJpaTest no verificamos contratos HTTP, no comparamos JSON, no evaluamos mensajes de validación ni comprobamos el funcionamiento de @ControllerAdvice. Todo eso ya estaba (y estaba bien) en la capa web. Tampoco trasladamos aquí las reglas de PublicationPolicy y la orquestación de ArticleWorkflowService, porque pertenecen a la lógica de negocio y están cubiertas por pruebas unitarias y (más adelante) escenarios de integración.
La prueba data‑slice responde a preguntas más mundanas pero críticamente importantes: «¿Puedo guardar una entidad en la base de datos?», «¿Puedo recuperarla?», «¿Lo que busco por un campo clave (por ejemplo, slug) realmente se encuentra?». Y en esto hay una belleza: la prueba se vuelve corta, honesta y muy clara en el diagnóstico. Falla no porque «toda la aplicación no arrancó», sino porque una parte concreta de la capa de persistencia se comporta de manera diferente.
9. Errores típicos al primer contacto con @DataJpaTest
Error nº1: esperar que en @DataJpaTest estén disponibles controladores y servicios.
Es un hábito frecuente después de @SpringBootTest: queremos que «todo esté a mano». Pero el data slice limita específicamente el contexto. Si necesitas un servicio, ya es otro nivel de prueba. Si arrastras un servicio a una prueba de datos, corres el riesgo de empezar a verificar lógica de negocio a través de la base de datos, lo cual es un camino costoso y confuso.
Error nº2: intentar probar la semántica HTTP en una prueba de repositorio.
En una prueba de repositorio no hay estados 200/400, ni JSON, ni MockMvc. Y esto no es una «incomodidad», sino una protección contra la mezcla de capas. Si te sorprendes con ganas de verificar Content-Type en @DataJpaTest, probablemente simplemente abriste la puerta equivocada.
Error nº3: importar la mitad de la configuración en @DataJpaTest «para que funcione».
A veces el estudiante ve un error de binding y decide: «Bueno, añadiré @Import(MyHugeConfig.class)». La prueba puede funcionar, pero perderás la principal ventaja de las pruebas slice: velocidad, localidad y límites claros. Al final obtienes un pequeño @SpringBootTest, solo que peor y sin un nombre honesto.
Error nº4: mockear el repositorio en una prueba de repositorio.
Esto parece paradójico, pero ocurre: «Estoy acostumbrado a mockear dependencias, vamos a mockear ArticleRepository». Entonces la prueba estará verificando… tu mock. Es decir, tu fantasía sobre cómo debería comportarse el repositorio. No es un mal género literario, pero es un género débil de pruebas.
Error nº5: escribir una única prueba enorme «para todo» en lugar de varias pequeñas.
Una prueba grande se convierte rápidamente en un escenario difícil de leer que falla «en algún punto intermedio». En la capa de datos es especialmente importante mantener las pruebas cortas: configuración — acción — verificación. Cuanta menos historia extraña haya en la prueba, más fácil será entender qué se rompió realmente.
GO TO FULL VERSION