Pruebas con Javay Spring
Rafael Gutiérrez (rafael.gutierrez@encora.com)
Encora
Septiembre 2025
2.
¿Por qué probarel software?
En general:
● El sistema cumple con los requerimientos (el qué) y especificaciones (el cómo).
● Encontrar errores antes de llegar a producción.
● Prevenir que nuevos cambios no rompan funcionalidad existente (regresión).
● Reducción de costos.
¿Cómo Software Developer?
● Confianza para realizar refactoring.
● Código bien probado es fácil de mantener.
● ¡Documentación viva!
● Colaboración de equipo
● Integración continua: Habilita el uso de flujos de despliegue automatizados confiables.
JUnit 5 -intro
● 1997 - Inventado durante un vuelo de Europa a EUA por Kent Beck y Erich Gamma
○ Kent Beck - XP, Agile Manifesto
○ Erich Gamma - uno de los autores del libro de patrones de diseño
● JUnit 5 en 2017
● Jars
○ Jupiter (test API)
○ Platform (correr las pruebas)
○ Vintage (soporte al JUnit “viejo”)
● JUnit 6 muy pronto
7.
JUnit 5 -ciclo de vida
● Instancia por método de prueba: ayuda a mantener las pruebas aisladas.
○ @TestInstance(Lifecycle.PER_METHOD), @TestInstance(Lifecycle.PER_CLASS)
● Ciclo de vida
○ @Test
○ @BeforeEach
○ @AfterEach
○ @BeforeAll
○ @AfterAll
● @DisplayName
● @Disabled
● @Order
● Demo: MyFirstTestExampleTest, LifecycleExampleTest
8.
JUnit 5 -assertions y assumptions
● Assertions: Ayudan a “verificar” la ejecución de la prueba.
● Métodos estáticos de org.junit.jupiter.api.Assertions.*:
○ assertEquals: verifica igualdad
○ assertTrue/assertFalse: Verifica que la condición se cumpla o no.
○ assertNull/assertNotNull: Verifica que la referencia sea o no nula.
○ assertThrows: Verifica que se tire una excepción al ejecutar un método.
○ assertAll: agrupa verificaciones
○ assertTimeout: valida tiempos de ejecución
○ etc.
● Assumptions: Usadas cuando no tiene sentido continuar con la ejecución si no hay condiciones adecuadas.
● Métodos estáticos de org.junit.jupiter.api.Assumptions.*:
○ assumeTrue/assumeFalse
○ assumeThat
○ etc.
● Demo: TimeTranslatorTest
9.
JUnit 5 -Pruebas parametrizadas
● Pruebas con la misma estructura donde solo varían los datos de entrada.
● @ParameterizedTest: a nivel método
● @ParameterizedClass: a nivel de clase (experimental)
● @ValueSource - valores fijos
● @EnumSource - enumeraciones
● @MethodSource - un método estático provee valores.
● @FieldSource - un atributo estático provee valores.
● @CvsSource, @CvsFileSource - formato CVS
● @ArgumentsSource - clase implementando ArgumentsProvider
● Mismo ciclo de vida que @Test.
● Demo: TimeTranslatorParameterizedTest
10.
JUnit 5 -Pruebas dinámicas
● @TestFactory
● Similares a las pruebas parametrizadas
● Ciclo de vida distinto, solo un @BeforeEach, @AfterEach por todo el conjunto de pruebas.
● Útiles cuando las pruebas no pueden ser expresadas en tiempo de compilación y se
necesita “crearlas” en tiempo de ejecución.
● Son fábrica de pruebas generadas en “runtime”.
● Demo: TimeTranslatorDynamicTest
11.
JUnit - Pruebasanidadas
● @Nested
● Ofrecen más capacidades de expresar relaciones entre grupos de pruebas.
● Hacen uso de clases anidadas.
● Facilitan pensar en la estructura de la prueba como una jerarquía.
● “A something, when X and Y happen, and Z is present, A should be ...”
● Demo: ActivityControllerSpringBootTest vs ActivityControllerSpringBootNestedTest
12.
Hamcrest y AssertJ
●La biblioteca de assertions de JUnit es buena, pero, IMHO, no suficiente.
● Las pruebas deben comunicar su intención claramente.
● Hamcrest: biblioteca de “matchers” que pueden ser combinados para expresar mejor la
“intención” de los assertions.
● AssertJ: biblioteca de assertions “fluidas”
● Mejores mensajes de error
● Demo: BetterAssertionsDemoTest
Dobles de pruebas(1)
● “Impostores” que ayudan a aislar el código
que se quiere probar
● Más rápidos que usar una implementación
real.
● Hacen la ejecución de la prueba determinista.
● Simulan condiciones especiales
● Exponen información oculta que ayuda a
validar.
15.
Dobles de pruebas(2)
● Test Stub - Dan respuestas predefinidas, útiles para comportamientos deterministas.
● Test Spy - “graban” información al ser llamados, utiles para
● Mock Object - pre-programados con “expectativas”. Tiran errores en caso de no recibir las
llamadas que se esperan (mock frameworks)
● Fake Object - implementaciones que funcionan, pero toman “shortcuts” (ejemplo, una in-memory
database)
● Dummy* - solo para “rellenar”, aunque no son realmente usados.
*Variación de Fake Object
16.
Mock Frameworks
● Mockito,EasyMock, PowerMock, etc.
● Funcionalidades:
● Creación de “dobles de pruebas” (completos y parciales)
● Especifican comportamiento
● Control en los valores de retorno
● Control en el “lanzamiento” de excepciones
● Comparación “Matching” de argumentos
● Verificación de llamadas
● “Espiar” objetos, llamadas y argumentos
17.
Mockito y TestStubs
● Reemplaza objetos reales con objetos específicos de prueba.
● Ayuda a devolver “entradas indirectas” deseadas desde otros componentes al SUT (Sistema bajo prueba, System
under test).
● Responder: Devuelve “entradas indirectas” válidas.
● Saboteur: Devuelve “entradas indirectas” inválidas.
● Temporary Test Stub: usado cuando el componente del cual se depende aún no está listo (está en desarrollo).
● Entity Chain Snipping: como un Responder pero para reemplazar algún objeto complejo de crear que usa el SUT.
● Hard-Coded Test Stub
● Configurable Test Stub
● Procedural Test Stub
● Demo: MockitoTestStubDemoTest
18.
Mockito y TestSpies
● Ayuda a verificar el “comportamiento”.
● Ayuda a capturar las “salidas indirectas” del SUT a otros componentes, para verificarlos después.
● Retrieval Interface: un test double que expone métodos para obtener la información “grabada”.
● Self Shunt: Cuando la misma clase de prueba se instala como el doble de pruebas espía.
● Inner Test Double: Se crea una clase interna anónima como doble de prueba espía.
● Indirect Output Registry
● Ver: MockitoTestSpyDemoTest
19.
Mockito y MockObjects
● Muy similares a los stubs, pero podemos implementar verificación de “comportamiento”.
● Se reemplaza el objeto real con un doble de pruebas que se puede verificar que fue usado
correctamente por el SUT.
● Diferencia con los stubs: la verificación final.
● Demo: MockitoMockObjectDemoTest
20.
Mockito y FakeObjects
● Ayudan a probar nuestro código cuando los componentes de los que se depende no
existen aún.
● Poder probar nuestro código con componentes más “ligeros” y evitar pruebas lentas.
● Fake Database
● In-Memory Database
● Fake Web Service
● Fake Service Layer
● Demo: ejemplos anteriores
¡Nada!
● No necesitaslevantar el contexto de Spring.
● Inyección de dependencias facilita usar dobles de prueba
23.
Soporte a pruebasde Spring
● El framework de Spring provee soporte de “primera clase” a las pruebas de integración con el módulo spring-test.
● El soporte a pruebas de Spring Boot está en los módulos spring-boot-test y spring-boot-test-autoconfigure.
● Starter: spring-boot-startet-test
○ Incluye bibliotecas como: JUnit 5, Spring Test, AssertJ, Hamcrest, Mockito, JSONassert, JsonPath, Awaitility.
● @SpringBootTest crea un ApplicationContext y/o WebServerApplicationContext
● Pruebas de “capas” con @*Test
● @TestConfiguration
● @ActiveProfiles
● @TestPropertySource
24.
@ExtendWith(MockitoExtension.class), @Mock, @InjectMocks
●Ayuda a probar la capa de servicios.
● Mocks para la capa de repositorios y otros servicios.
● @Mock e @InjectMocks: soporte de Mockito a mocks usando anotaciones para JUnit.
● Demo: ActivityServiceTest
25.
@JsonTest
● Ayuda aprobar la serialización y deserialización de objetos.
● Autoconfigura el soporte a JSON mappers de acuerdo a las dependencias.
○ Jackson (el más común)
○ Gson
○ Jsonb
● Se incluye soporte basado en AssertJ para JSONAssert y JsonPath para validar JSON.
○ JSONAssert: verifica JSON como si fueran String
○ JsonPath: navegar un JSON, hacer consultas a JSON
● Se puede usar Hamcrest.
● Demo: ActivityDTOJsonTest
26.
@WebMvcTest
● Ayuda aprobar la capa de controllers.
● Autoconfigura Spring MVC
● Escanea solamente @Controller, @ControllerAdvice, @JsonComponent, etc.
● @Component y @ConfigurationProperties no son escaneados.
● Pensado para probar un solo controller por clase de prueba.
● Demo: ActivityControllerWebMvcTest
27.
@DataJpaTest
● Escanea @Entityclases
● Configura repositorios Spring Data JPA
● Si una base de datos embebida está en el classpath, la usa.
○ IMHO, yo prefiero Testcontainers y usar base de datos real.
● Demo: ActivityRepositoryH2DataJpaTest
28.
● Es importantepoder ejecutar pruebas de integración sin requerir del despliegue de la aplicación en
servidor u otro tipo de infraestructura que el sistema use.
● Puede levantar un servidor, pero por defecto no lo hace.
● Atributo webEnvironment:
○ MOCK (predeterminado), RANDOM_PORT, DEFINED_PORT, NONE
● @WebTestClient: Usa WebClient internamente y usa una API fluida para verificar respuestas.
● @TestRestTemplate: alternativa al RestTemplate para pruebas integrales
● Demo: ActivityServiceIntegrationTest
● Demo: ActivityControllerSpringBootTest
@SpringBootTest
29.
Testcontainers
● Servicios corriendodentro de contenedores docker.
● Integrado con JUnit, permite correr un contenedor antes de correr las pruebas.
● Útil para pruebas integrales a servicios backend reales como Postgres, MySQL, MongoDB, Cassandra,
etc.
● Contenedores administrados por Spring como @Bean
● Contenedores administrados por Testcontainers usando las extensiones de JUnit: @Testcontainers,
@Container, @DynamicPropertySource
● Demo: ActivityRepositoryWithTestContainerDataJpaTest
● Demo: ActivityServiceIntegrationTest
¿Qué probar?
● Pruebacomportamiento, no implementación.
● Prueba la interfaz pública de los componentes.
● Prueba casos “extremos” (edge cases) y casos de error.
● Prueba lógica de negocio.
● Prueba puntos de integración entre componentes.
● Prueba flujos de negocio.
● ¿Ocurrió un bug? Reproduce el error con una prueba y solo después arregla el issue.
○ Práctica ágil de eXtreme Programming y Test Driven Development. Red-Green-Refactor.
● Usa la cobertura de código para encontrar flujos de negocio no probados.
33.
Buenas prácticas
● PatrónAAA (Arrange, Act, Assert) para mantener las pruebas organizadas y consistentes.
● Probar una sola cosa por método (conceptualmente hablando).
● Cuida tu código de prueba como el código de producción.
● DRY, KISS
● Asegúrate de que la prueba falla.
● Evita aserciones primitivas.
○ Puedes incluso crear tu “biblioteca” de aserciones muy ligada al lenguaje de dominio.
● Código claro, que las pruebas comuniquen bien su “intención”.
● Crea “test fixtures” reutilizables y fáciles de modificar para casos nuevos.
○ Patrón Builder/Factories
34.
“I'm not agreat programmer; I'm
just a good programmer with
great habits.”
― Kent Beck