Junit Assertions
Junit: Assertions
JUnit Jupiter incluye muchos de los métodos de aserción que ya estaban disponibles en JUnit 4, y además añade algunos nuevos que se integran bien con expresiones lambda de Java 8. Todas las aserciones de JUnit Jupiter son métodos estáticos de la clase org.junit.jupiter.api.Assertions.
Los métodos de aserción aceptan opcionalmente un mensaje como tercer parámetro, el cual puede ser una cadena de texto (String) o un Supplier<String>.
Cuando se utiliza un Supplier<String> (por ejemplo, una expresión lambda), el mensaje se evalúa de forma perezosa (lazy). Esto puede ofrecer una mejora en el rendimiento, especialmente si la construcción del mensaje es compleja o lleva mucho tiempo, ya que solo se evalúa si la aserción falla.
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.CountDownLatch;
import example.domain.Person;
import example.util.Calculator;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
class AssertionsDemo {
private final Calculator calculator = new Calculator();
private final Person person = new Person("Jane", "Doe");
@Test
void standardAssertions() {
assertEquals(2, calculator.add(1, 1));
assertEquals(4, calculator.multiply(2, 2),
"The optional failure message is now the last parameter");
// Lazily evaluates generateFailureMessage('a','b').
assertTrue('a' < 'b', () -> generateFailureMessage('a','b'));
}
@Test
void groupedAssertions() {
// In a grouped assertion all assertions are executed, and all
// failures will be reported together.
assertAll("person",
() -> assertEquals("Jane", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName())
);
}
@Test
void dependentAssertions() {
// Within a code block, if an assertion fails the
// subsequent code in the same block will be skipped.
assertAll("properties",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName);
// Executed only if the previous assertion is valid.
assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("e"))
);
},
() -> {
// Grouped assertion, so processed independently
// of results of first name assertions.
String lastName = person.getLastName();
assertNotNull(lastName);
// Executed only if the previous assertion is valid.
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
);
}
);
}
@Test
void exceptionTesting() {
Exception exception = assertThrows(ArithmeticException.class, () ->
calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());
}
@Test
void timeoutNotExceeded() {
// The following assertion succeeds.
assertTimeout(ofMinutes(2), () -> {
// Perform task that takes less than 2 minutes.
});
}
@Test
void timeoutNotExceededWithResult() {
// The following assertion succeeds, and returns the supplied object.
String actualResult = assertTimeout(ofMinutes(2), () -> {
return "a result";
});
assertEquals("a result", actualResult);
}
@Test
void timeoutNotExceededWithMethod() {
// The following assertion invokes a method reference and returns an object.
String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
assertEquals("Hello, World!", actualGreeting);
}
@Test
void timeoutExceeded() {
// The following assertion fails with an error message similar to:
// execution exceeded timeout of 10 ms by 91 ms
assertTimeout(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
Thread.sleep(100);
});
}
@Test
void timeoutExceededWithPreemptiveTermination() {
// The following assertion fails with an error message similar to:
// execution timed out after 10 ms
assertTimeoutPreemptively(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
new CountDownLatch(1).await();
});
}
private static String greeting() {
return "Hello, World!";
}
private static String generateFailureMessage(char a, char b) {
return "Assertion messages can be lazily evaluated -- "
+ "to avoid constructing complex messages unnecessarily." + (a < b);
}
}
❕❕Preemptive Timeouts with
assertTimeoutPreemptively()
Los diversos métodos assertTimeoutPreemptively() de la clase Assertions ejecutan el executable o supplier proporcionado en un hilo diferente al del código que realiza la llamada. Este comportamiento puede provocar efectos secundarios no deseados si el código que se ejecuta dentro del executable o supplier depende del almacenamiento mediante java.lang.ThreadLocal.
Un ejemplo común de esto es el soporte para pruebas transaccionales en Spring Framework. Específicamente, el soporte de pruebas de Spring vincula el estado de la transacción al hilo actual (a través de un ThreadLocal) antes de que se invoque un método de prueba. Como resultado, si el executable o supplier proporcionado a assertTimeoutPreemptively() invoca componentes administrados por Spring que participan en transacciones, las acciones realizadas por esos componentes no se revertirán con la transacción administrada por la prueba. Por el contrario, dichas acciones se confirmarán (commit) en el almacén persistente (por ejemplo, una base de datos relacional), incluso si la transacción administrada por la prueba es revertida.
También se pueden encontrar efectos secundarios similares con otros frameworks que dependen de almacenamiento mediante ThreadLocal.
Third-party Assertion Libraries
Aunque las facilidades de aserción proporcionadas por JUnit Jupiter son suficientes para muchos escenarios de prueba, hay momentos en los que se desea o se requiere mayor potencia y funcionalidad adicional, como los matchers. En tales casos, el equipo de JUnit recomienda el uso de bibliotecas de aserción de terceros como AssertJ, Hamcrest, Truth, etc. Por lo tanto, los desarrolladores son libres de usar la biblioteca de aserciones que prefieran.
Por ejemplo, la combinación de matchers y una API fluida (fluent API) puede usarse para hacer que las aserciones sean más descriptivas y legibles. Sin embargo, la clase org.junit.jupiter.api.Assertions de JUnit Jupiter no proporciona un método assertThat() como el que se encuentra en la clase org.junit.Assert de JUnit 4, el cual acepta un Matcher de Hamcrest. En su lugar, se anima a los desarrolladores a utilizar el soporte incorporado para matchers que proporcionan las bibliotecas de aserción de terceros.
El siguiente ejemplo demuestra cómo usar el soporte assertThat() de Hamcrest en una prueba de JUnit Jupiter. Siempre que la biblioteca Hamcrest se haya añadido al classpath, se pueden importar estáticamente métodos como assertThat(), is() y equalTo(), y luego usarlos en pruebas como en el método assertWithHamcrestMatcher() que se muestra a continuación.
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
class HamcrestAssertionsDemo {
private final Calculator calculator = new Calculator();
@Test
void assertWithHamcrestMatcher() {
assertThat(calculator.subtract(4, 1), is(equalTo(3)));
}
}
Naturalmente, las pruebas heredadas basadas en el modelo de programación de JUnit 4 pueden continuar utilizando org.junit.Assert#assertThat.