A maioria das soluções
- finalizar o teste (método, não a execução inteira), o momento
System.exit()
é chamado
- ignore um já instalado
SecurityManager
- Às vezes, seja bastante específico para uma estrutura de teste
- restrito a ser usado no máximo uma vez por caso de teste
Portanto, a maioria das soluções não é adequada para situações em que:
- A verificação dos efeitos colaterais deve ser realizada após a chamada para
System.exit()
- Um gerenciador de segurança existente faz parte do teste.
- Uma estrutura de teste diferente é usada.
- Você deseja ter várias verificações em um único caso de teste. Isso pode não ser estritamente recomendado, mas pode ser muito conveniente às vezes, especialmente em combinação com
assertAll()
, por exemplo.
Não fiquei satisfeito com as restrições impostas pelas soluções existentes apresentadas nas outras respostas e, portanto, criei algo sozinho.
A classe a seguir fornece um método assertExits(int expectedStatus, Executable executable)
que afirma que System.exit()
é chamado com um status
valor especificado e o teste pode continuar após ele. Funciona da mesma maneira que o JUnit 5 assertThrows
. Ele também respeita um gerenciador de segurança existente.
Há um problema restante: quando o código em teste instala um novo gerenciador de segurança que substitui completamente o gerenciador de segurança definido pelo teste. Todas as outras SecurityManager
soluções baseadas em mim conhecidas sofrem o mesmo problema.
import java.security.Permission;
import static java.lang.System.getSecurityManager;
import static java.lang.System.setSecurityManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public enum ExitAssertions {
;
public static <E extends Throwable> void assertExits(final int expectedStatus, final ThrowingExecutable<E> executable) throws E {
final SecurityManager originalSecurityManager = getSecurityManager();
setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(final Permission perm) {
if (originalSecurityManager != null)
originalSecurityManager.checkPermission(perm);
}
@Override
public void checkPermission(final Permission perm, final Object context) {
if (originalSecurityManager != null)
originalSecurityManager.checkPermission(perm, context);
}
@Override
public void checkExit(final int status) {
super.checkExit(status);
throw new ExitException(status);
}
});
try {
executable.run();
fail("Expected System.exit(" + expectedStatus + ") to be called, but it wasn't called.");
} catch (final ExitException e) {
assertEquals(expectedStatus, e.status, "Wrong System.exit() status.");
} finally {
setSecurityManager(originalSecurityManager);
}
}
public interface ThrowingExecutable<E extends Throwable> {
void run() throws E;
}
private static class ExitException extends SecurityException {
final int status;
private ExitException(final int status) {
this.status = status;
}
}
}
Você pode usar a classe assim:
@Test
void example() {
assertExits(0, () -> System.exit(0)); // succeeds
assertExits(1, () -> System.exit(1)); // succeeds
assertExits(2, () -> System.exit(1)); // fails
}
O código pode ser facilmente portado para JUnit 4, TestNG ou qualquer outra estrutura, se necessário. O único elemento específico da estrutura está falhando no teste. Isso pode ser facilmente alterado para algo independente de estrutura (exceto um Junit 4 Rule
Há espaço para melhorias, por exemplo, sobrecarregando assertExits()
com mensagens personalizáveis.