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 statusvalor 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 SecurityManagersoluçõ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.