JUnit
JUnit 5
JUnit 5 (Jupiter) es el estándar para tests unitarios en Java.
Dependencia
Maven:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope></dependency>Gradle:
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'Anotaciones principales
@Test
Marca un método como test.
@Testpublic void deberiaCalcularSuma() { assertEquals(5, 2 + 3);}@DisplayName
Nombre legible para el test.
@Test@DisplayName("Debería calcular correctamente el área de un círculo")public void testAreaCirculo() { // ...}@Disabled
Deshabilita un test temporalmente.
@Test@Disabled("Funcionalidad no implementada aún")public void testFuturo() { // ...}Assertions
Básicos
// IgualdadassertEquals(esperado, actual);assertEquals(5, calc.sumar(2, 3));
// Verdadero/FalsoassertTrue(condicion);assertFalse(condicion);
// NullassertNull(objeto);assertNotNull(objeto);
// Misma instanciaassertSame(esperado, actual);assertNotSame(esperado, actual);Con mensaje personalizado
assertEquals(5, calc.sumar(2, 3), "La suma debería ser 5");assertAll
Ejecuta todos los assertions incluso si algunos fallan.
@Testpublic void deberiaValidarUsuario() { Usuario usuario = new Usuario("Juan", "juan@email.com", 25);
assertAll("usuario", () -> assertEquals("Juan", usuario.getNombre()), () -> assertEquals("juan@email.com", usuario.getEmail()), () -> assertEquals(25, usuario.getEdad()) );}assertThrows
Verifica que se lance una excepción.
@Testpublic void deberiaLanzarExcepcionAlDividirPorCero() { Calculadora calc = new Calculadora();
assertThrows(ArithmeticException.class, () -> { calc.dividir(10, 0); });}
// Con verificación del mensajeException exception = assertThrows(IllegalArgumentException.class, () -> { validador.validar("");});assertEquals("Valor no puede estar vacío", exception.getMessage());assertTimeout
Verifica que el código se ejecute en un tiempo límite.
@Testpublic void deberiaCompletarEnMenosDe1Segundo() { assertTimeout(Duration.ofSeconds(1), () -> { // Código que debe completarse rápido Thread.sleep(500); });}Ciclo de vida
@BeforeEach / @AfterEach
public class CalculadoraTest { private Calculadora calculadora;
@BeforeEach public void setUp() { System.out.println("Antes de cada test"); calculadora = new Calculadora(); }
@AfterEach public void tearDown() { System.out.println("Después de cada test"); calculadora = null; }
@Test public void test1() { // calculadora está inicializada }
@Test public void test2() { // calculadora está inicializada (nueva instancia) }}@BeforeAll / @AfterAll
Métodos estáticos que se ejecutan una vez.
public class DatabaseTest { private static Database db;
@BeforeAll public static void inicializarBaseDatos() { System.out.println("Conectando a base de datos..."); db = new Database(); db.conectar(); }
@AfterAll public static void cerrarBaseDatos() { System.out.println("Cerrando conexión..."); db.desconectar(); }
@Test public void test1() { // db ya está conectada }}Tests parametrizados
@ValueSource
@ParameterizedTest@ValueSource(ints = {2, 4, 6, 8, 10})public void deberiaSer Par(int numero) { assertTrue(numero % 2 == 0);}
@ParameterizedTest@ValueSource(strings = {"Juan", "María", "Pedro"})public void deberiaSerNombreValido(String nombre) { assertFalse(nombre.isEmpty());}@CsvSource
@ParameterizedTest@CsvSource({ "1, 2, 3", "10, 20, 30", "100, 200, 300"})public void deberiaSumar(int a, int b, int esperado) { assertEquals(esperado, a + b);}@MethodSource
@ParameterizedTest@MethodSource("proveerNumeros")public void deberiaSerPositivo(int numero) { assertTrue(numero > 0);}
static Stream<Integer> proveerNumeros() { return Stream.of(1, 2, 3, 4, 5);}@CsvFileSource
@ParameterizedTest@CsvFileSource(resources = "/datos-test.csv", numLinesToSkip = 1)public void testDesdeCsv(String nombre, int edad) { assertNotNull(nombre); assertTrue(edad > 0);}Tests condicionales
@EnabledOnOs / @DisabledOnOs
@Test@EnabledOnOs(OS.LINUX)public void soloEnLinux() { // Solo se ejecuta en Linux}
@Test@DisabledOnOs(OS.WINDOWS)public void noEnWindows() { // Se ejecuta en todo excepto Windows}@EnabledIf / @DisabledIf
@Test@EnabledIf("esEntornoDesarrollo")public void soloEnDev() { // ...}
boolean esEntornoDesarrollo() { return "dev".equals(System.getenv("ENV"));}Assumptions
Ejecuta un test solo si se cumple una condición.
@Testpublic void testConAssumption() { assumeTrue("dev".equals(System.getenv("ENV"))); // Solo continúa si ENV=dev
// Resto del test}
@Testpublic void testConAssumingThat() { assumingThat("dev".equals(System.getenv("ENV")), () -> { // Solo ejecuta esto si ENV=dev } );
// Esto siempre se ejecuta}Nested Tests
Organiza tests relacionados.
@DisplayName("Tests de Stack")public class StackTest {
@Nested @DisplayName("Cuando está vacío") class WhenEmpty { private Stack<Integer> stack;
@BeforeEach void crearStack() { stack = new Stack<>(); }
@Test @DisplayName("debería estar vacío") void estaVacio() { assertTrue(stack.isEmpty()); }
@Test @DisplayName("debería lanzar excepción al hacer pop") void popLanzaExcepcion() { assertThrows(EmptyStackException.class, stack::pop); } }
@Nested @DisplayName("Cuando tiene elementos") class WhenNotEmpty { private Stack<Integer> stack;
@BeforeEach void crearStackConElementos() { stack = new Stack<>(); stack.push(1); stack.push(2); }
@Test @DisplayName("no debería estar vacío") void noEstaVacio() { assertFalse(stack.isEmpty()); }
@Test @DisplayName("debería retornar el último elemento") void popRetornaUltimo() { assertEquals(2, stack.pop()); } }}Timeouts
@Timeout
@Test@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)public void deberiaCompletarRapido() { // Falla si tarda más de 500ms}Repeated Tests
Ejecuta un test múltiples veces.
@RepeatedTest(10)public void testRepetido(RepetitionInfo info) { System.out.println("Ejecución " + info.getCurrentRepetition() + " de " + info.getTotalRepetitions());}Orden de ejecución
Por defecto, JUnit no garantiza orden. Si necesitas orden:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)public class OrdenadoTest {
@Test @Order(1) public void primero() { }
@Test @Order(2) public void segundo() { }
@Test @Order(3) public void tercero() { }}Cuidado: Tests no deberían depender del orden. Úsalo solo cuando sea necesario.
Buenas prácticas
- Nombres descriptivos: Usa
@DisplayNameo nombres largos. - Un concepto por test: Cada test prueba una cosa.
- AAA pattern: Arrange, Act, Assert.
- Tests independientes: No deben depender unos de otros.
- Tests rápidos: Los tests unitarios deben ser muy rápidos.
Próximo paso
Aprende a usar mocks para aislar tests: Mockito →