Como afirmo minha mensagem de exceção com anotação de teste de Junit?
-
20-09-2019 - |
Pergunta
Eu escrevi alguns testes Junit com @Test
anotação. Se meu método de teste lançar uma exceção verificada e se eu quiser afirmar a mensagem junto com a exceção, existe uma maneira de fazê -lo com Junit @Test
anotação? Afaik, Junit 4.7 não fornece esse recurso, mas as versões futuras o fornecem? Eu sei que no .NET você pode afirmar a mensagem e a classe de exceção. Procurando uma característica semelhante no mundo Java.
É isso que eu quero:
@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}
Solução
Você poderia usar o @Rule
anotação com ExpectedException
, assim:
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
expectedEx.expect(RuntimeException.class);
expectedEx.expectMessage("Employee ID is null");
// do something that should throw the exception...
System.out.println("=======Starting Exception process=======");
throw new NullPointerException("Employee ID is null");
}
Observe que o exemplo no ExpectedException
Docs está (atualmente) errado - não há construtor público, então você tem que usar ExpectedException.none()
.
Outras dicas
eu gosto de @Rule
responda. No entanto, se, por algum motivo, você não quiser usar regras. Há uma terceira opção.
@Test (expected = RuntimeException.class)
public void myTestMethod()
{
try
{
//Run exception throwing operation here
}
catch(RuntimeException re)
{
String message = "Employee ID is null";
assertEquals(message, re.getMessage());
throw re;
}
fail("Employee Id Null exception did not throw!");
}
Você tem que usar @Test(expected=SomeException.class)
? Quando precisamos afirmar a mensagem real da exceção, é isso que fazemos.
@Test
public void myTestMethod()
{
try
{
final Integer employeeId = null;
new Employee(employeeId);
fail("Should have thrown SomeException but did not!");
}
catch( final SomeException e )
{
final String msg = "Employee ID is null";
assertEquals(msg, e.getMessage());
}
}
No Junit 4.13 (uma vez lançado), você pode fazer:
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
...
@Test
void exceptionTesting() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> { throw new IllegalArgumentException("a message"); }
);
assertEquals("a message", exception.getMessage());
}
Isso também funciona em Junit 5 Mas com diferentes importações:
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
...
Na verdade, o melhor uso é com tentativa/captura. Por quê? Porque você pode controlar o local onde espera a exceção.
Considere este exemplo:
@Test (expected = RuntimeException.class)
public void someTest() {
// test preparation
// actual test
}
E se um dia o código for modificado e a preparação do teste lançará uma tempo de execução? Nesse caso, o teste real nem é testado e, mesmo que não faça nenhuma exceção, o teste passará.
É por isso que é muito melhor usar o Try/Catch do que confiar na anotação.
Raystorm teve uma boa resposta. Também não sou um grande fã de regras. Faço algo semelhante, exceto que crio a seguinte classe de utilidade para ajudar a legibilidade e usabilidade, que é uma das grandes vantagens de anotações em primeiro lugar.
Adicione esta classe de utilitário:
import org.junit.Assert;
public abstract class ExpectedRuntimeExceptionAsserter {
private String expectedExceptionMessage;
public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
this.expectedExceptionMessage = expectedExceptionMessage;
}
public final void run(){
try{
expectException();
Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
} catch (RuntimeException e){
Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
}
}
protected abstract void expectException();
}
Então, para o meu teste de unidade, tudo o que preciso é este código:
@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
@Override
protected void expectException() {
throw new RuntimeException("anonymous user can't access privileged resource");
}
}.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}
Se estiver usando @Rule, o conjunto de exceções é aplicado a todos os métodos de teste na classe de teste.
Eu gosto da resposta do User64141, mas descobri que poderia ser mais generalizada. Aqui está minha opinião:
public abstract class ExpectedThrowableAsserter implements Runnable {
private final Class<? extends Throwable> throwableClass;
private final String expectedExceptionMessage;
protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
this.throwableClass = throwableClass;
this.expectedExceptionMessage = expectedExceptionMessage;
}
public final void run() {
try {
expectException();
} catch (Throwable e) {
assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
return;
}
fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
}
protected abstract void expectException();
}
Observe que deixar a declaração "Fail" no bloco de tentativa faz com que a exceção de afirmação relacionada seja capturada; Usar o retorno na instrução Catch impede isso.
Importar o Exceção de captura biblioteca e use isso. É muito mais limpo que o ExpectedException
regra ou a try-catch
.
Exemplo de seus documentos:
import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;
// given: an empty list
List myList = new ArrayList();
// when: we try to get the first element of the list
catchException(myList).get(1);
// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
allOf(
instanceOf(IndexOutOfBoundsException.class),
hasMessage("Index: 1, Size: 0"),
hasNoCause()
)
);
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
//test code
}