Introducción: ¿Por qué son necesarias las pruebas en Java?
\nCada desarrollador Java se ha enfrentado al menos una vez a la situación en la que, después de cambiar una línea de código, todo el sistema «se rompía». Para evitar tales catástrofes, la industria ha desarrollado un sistema de pruebas sólido. En este artículo, analizaremos tres niveles de pruebas en Java: unit (pruebas unitarias), integration (pruebas de integración) y e2e (pruebas de extremo a extremo). Aprenderás en qué se diferencian, qué herramientas usar y cómo escribir pruebas correctamente en Java.
\nIndependientemente de si escribes microservicios con Spring Boot o aplicaciones de escritorio, comprender la pirámide de pruebas es una habilidad obligatoria. ¡Allá vamos!
\n\n1. Pruebas unitarias: El fundamento de la calidad
\nLas pruebas unitarias verifican el funcionamiento de clases o métodos individuales de forma aislada de las dependencias externas. Este es el nivel de pruebas más rápido y económico.
\n\n1.1 Herramientas principales
\n- \n
- JUnit 5 — el framework estándar para Java (la última versión es Jupiter). \n
- Mockito — biblioteca para crear objetos mock (simulaciones). \n
- AssertJ — biblioteca con un rico conjunto de aserciones fluidas. \n
1.2 Ejemplo de prueba unitaria con JUnit 5 y Mockito
\nSupongamos que tenemos un servicio UserService que depende del repositorio UserRepository:
public class UserService {\n private final UserRepository repository;\n\n public UserService(UserRepository repository) {\n this.repository = repository;\n }\n\n public String getUserName(Long id) {\n User user = repository.findById(id);\n return user != null ? user.getName() : "Unknown";\n }\n}\nEscribamos una prueba unitaria, aislando la dependencia externa mediante Mockito:
\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(MockitoExtension.class)\nclass UserServiceTest {\n\n @Mock\n private UserRepository userRepository;\n\n @InjectMocks\n private UserService userService;\n\n @Test\n void testGetUserName_WhenUserExists() {\n // Arrange\n User mockUser = new User(1L, "Alice");\n when(userRepository.findById(1L)).thenReturn(mockUser);\n\n // Act\n String result = userService.getUserName(1L);\n\n // Assert\n assertEquals("Alice", result);\n }\n\n @Test\n void testGetUserName_WhenUserNotFound() {\n when(userRepository.findById(99L)).thenReturn(null);\n\n String result = userService.getUserName(99L);\n\n assertEquals("Unknown", result);\n }\n}\nRegla de buena práctica: una prueba — una verificación. Utiliza nombres de métodos claros (por ejemplo, testGetUserName_WhenUserExists).
\n\n2. Pruebas de integración: Verificación de interacciones
\nLas pruebas de integración verifican cómo los componentes del sistema funcionan juntos: base de datos, sistema de archivos, API externas. Son más lentas que las pruebas unitarias, pero brindan mayor confianza en la corrección de las conexiones.
\n\n2.1 Herramientas para pruebas de integración en Java
\n- \n
- Spring Boot Test —
@SpringBootTestpara levantar el contexto de la aplicación. \n - Testcontainers — ejecución de bases de datos reales (PostgreSQL, MySQL) en contenedores Docker. \n
- H2 — base de datos en memoria para pruebas rápidas. \n
2.2 Ejemplo de prueba de integración con Spring Boot y Testcontainers
\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.t