CodeGym /Cursos /Spring Test /@SpringBootTest: inicio y coste

@SpringBootTest: inicio y coste

Spring Test
Nivel 19, Lección 0
Disponible

1. Inicio completo con @SpringBootTest

Si antes probábamos zonas individuales de la aplicación, ahora probaremos el hecho mismo de que estas zonas forman un sistema funcional. @SpringBootTest es el momento en que Spring Boot deja de ser «un framework que te ayuda» y se convierte en «un enorme mecanismo que ejecutas en la prueba». Es útil, pero no es gratuito.

Imagina que estás revisando una bicicleta. Un test unitario es verificar que la cadena gira. Un slice-test es verificar que los frenos frenan. Y @SpringBootTest es sacar la bicicleta completa, sentar a una persona en ella y asegurarse de que realmente se mueva (o al menos no se desmorone al primer vistazo). La bicicleta puede estar ensamblada con piezas excelentes, pero si el manillar está atornillado al sillín, entonces… será muy divertido, pero no por mucho tiempo.

@SpringBootTest inicia tu aplicación casi de la misma manera que se inicia en la realidad. «Casi» porque el inicio de prueba generalmente difiere en la configuración (perfil test, otras propiedades), y a veces en si se incluye o no un entorno web completo. Pero la idea clave es esta: no es un trozo aislado de contexto, sino el ApplicationContext completo de tu aplicación Spring Boot.

Hasta ahora, hemos probado deliberadamente las capas por separado: unit, JSON, MVC slice, data slice. Ahora surge otro riesgo: cada pieza por separado es verde, pero la aplicación como sistema sigue sin ensamblarse. Por lo tanto, primero es importante entender la mecánica del contexto completo y su coste, luego la señal más burda del full-context «la aplicación realmente inicia», y después ir a escenarios más sustanciales a través de beans reales.

2. El ApplicationContext completo en la práctica

Las palabras «contexto completo» suenan a algo de fantasía: «invocación del espíritu de Spring Boot en luna llena». En la práctica, todo es más simple y pragmático: Spring ensambla el contenedor, encuentra configuraciones, crea beans, vincula dependencias, levanta la infraestructura. Y si en algún lugar de esta cadena hay un error, tu prueba falla antes de que hayas podido escribir siquiera una verificación.

¿Qué sucede realmente cuando se inicia @SpringBootTest?

Spring Boot toma tu clase principal @SpringBootApplication (por ejemplo, ContentHubApplication), levanta el ApplicationContext, se activa el mecanismo de auto-configuration, se leen las propiedades de application.yml y application-test.yml, se aplican overrides, se crea el entorno (Environment) y comienza el pipeline «crear todo lo que necesita la aplicación».

Si el proyecto tiene JPA, lo más probable es que se levante DataSource, EntityManagerFactory, repositorios, proveedor de transacciones. Si hay migraciones, Flyway puede intentar aplicarlas. Si hay Spring MVC, se ensamblará la infraestructura MVC. Si hay Spring Security, se levantarán los beans de seguridad y los filtros (incluso si hoy no estás probando la seguridad como requisito de negocio). En resumen, es un inicio real de la aplicación, solo que en modo de prueba.

Para que esta idea «asiente» visualmente, es útil ver la cadena en forma de diagrama:

flowchart TD
    T["Test class with @SpringBootTest"] --> R["Spring Test bootstrap"]
    R --> C["Create full ApplicationContext"]
    C --> A["Spring Boot auto-configuration"]
    A --> B["Create beans: controllers, services, repositories, config"]
    B --> I["Infrastructure: DataSource, JPA, Flyway, MVC, Security..."]
    I --> OK["Context started -> test method executes"]

Una consecuencia importante: en @SpringBootTest a menudo capturas errores «al inicio». Y eso es normal. Esta prueba está diseñada precisamente para decir: «La aplicación como sistema se ensambla».

Qué se levanta en ContentHub con @SpringBootTest

Cuando decimos «se levanta todo», es fácil para un principiante imaginar que en la prueba de repente se inician todos los servidores del mundo, se conectan todas las bases del universo y en algún lugar lejano se despierta el admin DBA. En realidad, se levanta lo que hay en tu aplicación, más la infraestructura que Spring Boot necesita para que esas partes funcionen.

En el proyecto ContentHub (una aplicación MVC clásica) se puede descomponer mentalmente «qué se levanta» en varias zonas grandes. Es conveniente verlo a través de una tabla: no como «lista de anotaciones», sino como «qué capa realmente se vuelve viva».

Zona del sistema Qué se levanta normalmente en full context Por qué es importante precisamente para la prueba
Configuración y propiedades Environment, binding @ConfigurationProperties, tu application-test.yml Captura errores como «renombraron una propiedad y la mitad de los beans ya no se crean»
Parte web Infraestructura MVC, controladores, convertidores, manejadores de excepciones Captura errores de cableado de la parte web, incluso si no pruebas HTTP como transporte
Capa de servicios tus @Service y otros componentes Muestra que los servicios de negocio realmente se crean y reciben dependencias
Persistencia DataSource, JPA, repositorios, transacciones, a veces Flyway Captura desajustes de esquema/configuración/mapeo y en general problemas de «la BD no se levantó»
Infraestructura de seguridad configuración de seguridad, filtros, user-details, etc. Captura errores en el cableado de seguridad (incluso si aún no verificas 401/403)
Integraciones tus adaptadores a sistemas externos (si están incluidos como beans) Captura problemas de inicio/configuración de la capa de integración

Un detalle sutil: en @SpringBootTest no pruebas «cada rama de negocio», sino la compatibilidad misma de los componentes. Por ejemplo, el servicio necesitaba un bean Clock y olvidaste registrarlo. O renombraste el paquete y el escaneo de componentes dejó de ver @Service. O tienes dos beans del mismo tipo sin calificador. Todo esto se captura perfectamente con un inicio full context.

3. Qué demuestra y qué no demuestra la prueba

A menudo se ve @SpringBootTest como una «superprueba». Dicen: si levantamos toda la aplicación, entonces «lo hemos probado todo». Es una lógica muy humana, pero lleva a una de las trampas más clásicas en las pruebas: la falsa confianza de una «gran prueba verde».

Lo que @SpringBootTest realmente demuestra: la aplicación se ensambla en un contexto unificado, los beans se crean, el cableado funciona, la configuración es válida (hasta donde Spring y tus reglas lo verifican al iniciar), la infraestructura arranca. Puede capturar defectos que no se ven a nivel de unit o slices, porque allí las partes de la aplicación se levantan de forma selectiva o manual.

Pero esto es lo que no demuestra automáticamente: no garantiza que la regla de negocio sea correcta (por ejemplo, que la transición de DRAFT a PUBLISHED esté prohibida si en tu capa de política lo rompieron). No garantiza que tu contrato JSON sea estable y no cambie «por pequeños detalles» (eso lo hacíamos con @JsonTest). No garantiza la corrección de consultas individuales de repositorio en todos los detalles (para eso usábamos @DataJpaTest). Tampoco se convierte por sí solo en una «prueba de internet real» o una «prueba del cliente móvil» — esos son tipos de pruebas completamente diferentes.

Una idea muy útil para la tranquilidad interna: @SpringBootTest es una verificación del sistema como mecanismo ensamblado, no la verificación de todos los engranajes por separado. Los engranajes aún se verifican más rápido y con mayor precisión en las capas unit/slice.

4. Coste del contexto completo: tiempo y diagnóstico

Si los tests unitarios son «rápido y claro», entonces @SpringBootTest es «minucioso y a veces doloroso». Y eso es normal: compras una evidencia más fuerte a costa de un mayor gasto. El problema comienza solo cuando compras esta evidencia donde no es necesaria.

Se vuelve costoso por tres razones. La primera razón: tiempo de inicio. Levantar un full context significa realizar un montón de trabajo: escaneo de componentes, procesamiento de configuraciones, construcción de proxies, creación de beans de infraestructura, posiblemente levantar DataSource y migraciones. Incluso si todo está configurado perfectamente, no es comparable a «crear un objeto con new y verificar un método».

La segunda razón: cantidad de posibles puntos de fallo. Un test unitario falla allí donde verificas. @SpringBootTest puede fallar incluso antes de tu método @Test: en la etapa de creación de cualquier bean. Por eso los mensajes de error suelen ser aterradores, verbosos y parecen como si Spring te hubiera escrito una novela del género «thriller de excepciones».

La tercera razón: complejidad del diagnóstico. Cuando falla un test unitario, abres el método y ves el problema. Cuando falla el contexto, debes entender: ¿es un error de configuración, conflicto de beans, propiedades incorrectas, problema de conexión a la BD, error en la migración, dependencia cíclica? Todo esto es solucionable, pero requiere costumbre.

Y sí, hay una «bonificación de ingeniería» (que no anula el coste): Spring puede almacenar en caché el contexto entre pruebas si es «el mismo». Esto ayuda a acelerar el suite, pero no convierte a @SpringBootTest en una verificación barata. Y si empiezas a agregar a cada prueba diferentes properties y @Import sin medida, entonces tú mismo estás multiplicando los contextos y matando la aceleración. (Ese es el momento en que las pruebas se vuelven «costosas por iniciativa propia»).

5. La señal más burda: la aplicación realmente inicia

Por ahora, sin servidores, puertos ni escenarios de negocio. Para un primer contacto, necesitamos la señal más burda: que @SpringBootTest obligue a la aplicación a pasar por el proceso de inicio.

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class ContentHubApplicationBootstrapTest {

    @Test
    void contextLoads() {
        // Si el contexto no se ensambla, la prueba fallará antes de ejecutar el método
    }
}

Incluso una prueba tan vacía ya es útil: verifica que la aplicación se ensambla como sistema en la configuración de test. Aquí todavía no importa en qué modo se levanta la parte web; necesitamos el hecho mismo del inicio del contexto. Todo lo demás — HTTP, límites transaccionales y un flujo de negocio real — son cuestiones aparte, y mezclarlos en el primer paso solo es perjudicial.

6. Cómo leer los fallos de @SpringBootTest

Las primeras caídas de @SpringBootTest a menudo son moralmente impactantes. Un test unitario falla ordenadamente: «esperaba 5, obtuve 7». Pero el full contexto a veces falla como si hubieras invocado accidentalmente a un antiguo dios de las excepciones: varias pantallas de stacktrace, mención de diez clases que no conocías, y la sensación de que no es Spring quien te regaña, sino todo el ecosistema Java.

Un hábito importante: casi siempre hay una causa raíz (root cause), simplemente está enterrada más abajo. Normalmente no buscas el «primer mensaje», sino el «más profundo», donde se dice algo concreto. Por ejemplo, el problema puede ser simple: «no se pudo crear DataSource porque no hay URL», o «hay dos beans del mismo tipo», o «conflicto de property binding». Todo lo demás arriba es solo el camino por el que Spring llegó a este problema.

Otro principio útil: que el contexto falle no significa que «la prueba esté rota». A menudo es una señal de que la aplicación como sistema no se está ensamblando. Y esa señal es valiosa, simplemente no promete ser agradable.

7. Errores típicos al primer contacto con @SpringBootTest

Error nº1: usar @SpringBootTest como prueba «por defecto» para todo.
Esto sucede de forma muy natural: la anotación parece poderosa, y por tanto «seguramente correcta». En realidad, pagas con tiempo y complejidad por una evidencia que a menudo no es necesaria. Si estás verificando lógica local como la generación de slugs o la matriz de transiciones de estado, el contexto completo aquí sería como llamar a los bomberos para encender una vela en la tarta.

Error nº2: confundir «el contexto se levantó» con «todo funciona».
Un inicio exitoso significa que el sistema se ensambla. Es una propiedad importante, pero no reemplaza ni los tests unitarios de reglas de negocio ni los slice-tests de contratos web/data/JSON. La aplicación puede iniciar perfectamente y aun así publicar un artículo de DRAFT a PUBLISHED sin revisión, porque alguien se equivocó en PublicationPolicy. El contexto estará feliz, pero el negocio no.

Error nº3: intentar «mejorar» el contexto completo mockeando la mitad de la aplicación.
A veces se quiere dentro de @SpringBootTest mockear todo lo que estorba: repositorios, servicios, integraciones, media sistema. Normalmente eso es una señal de que se ha elegido el nivel de prueba incorrecto. Si necesitas un fuerte aislamiento, entonces quieres unit o slice. El contexto completo es valioso precisamente porque es real.

Error nº4: olvidarse del entorno de prueba y los perfiles.
El full context es muy sensible a la configuración. Si no fijas el perfil de test y no controlas las propiedades, es fácil obtener «funciona en mi máquina, falla en la del compañero». La prueba debe iniciar en un entorno predecible, de lo contrario todo el sentido de la verificación «la aplicación se ensambla» se pierde: solo se ensamblará «a veces».

Error nº5: esperar simplicidad de diagnóstico, como en los tests unitarios.
@SpringBootTest verifica el sistema, por lo tanto también fallará «sistémicamente»: cableado, propiedades, infraestructura. Aquí es importante aceptar con calma que las primeras veces leerás el stacktrace más tiempo del que tardaste en escribir la prueba. Luego será más fácil: comenzarás a reconocer las excepciones típicas y encontrar la causa real más rápido.

Error nº6: romper el almacenamiento en caché del contexto sin necesidad.
Si usas sin medida diferentes conjuntos de properties, @Import o @DirtiesContext, entonces Spring deja de reutilizar el contexto ya levantado entre pruebas. Como resultado, las pruebas se vuelven de repente «lentas por sí solas», aunque la razón real es que estás creando constantemente nuevos contextos.

1
Tarea
Spring Test, nivel 19, lección 0
Bloqueada
Beans reales en el contexto completo
Beans reales en el contexto completo
1
Tarea
Spring Test, nivel 19, lección 0
Bloqueada
Bean de infraestructura y bean de aplicación
Bean de infraestructura y bean de aplicación
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION