La mayoría de las soluciones lo harán
- terminar la prueba (método, no toda la ejecución)
System.exit()
se llama el momento
- ignorar un ya instalado
SecurityManager
- A veces sea bastante específico para un marco de prueba
- restringir para ser utilizado al máximo una vez por caso de prueba
Por lo tanto, la mayoría de las soluciones no son adecuadas para situaciones en las que:
- La verificación de los efectos secundarios se realizará después de la llamada a
System.exit()
- Un administrador de seguridad existente es parte de las pruebas.
- Se utiliza un marco de prueba diferente.
- Desea tener múltiples verificaciones en un solo caso de prueba. Esto puede no ser estrictamente recomendado, pero a veces puede ser muy conveniente, especialmente en combinación con
assertAll()
, por ejemplo.
No estaba contento con las restricciones impuestas por las soluciones existentes presentadas en las otras respuestas y, por lo tanto, se me ocurrió algo por mi cuenta.
La siguiente clase proporciona un método assertExits(int expectedStatus, Executable executable)
que afirma que System.exit()
se llama con un status
valor especificado , y la prueba puede continuar después. Funciona de la misma manera que JUnit 5 assertThrows
. También respeta a un administrador de seguridad existente.
Hay un problema restante: cuando el código bajo prueba instala un nuevo administrador de seguridad que reemplaza completamente al administrador de seguridad establecido por la prueba. Todas las otras SecurityManager
soluciones basadas en mí que conozco sufren el mismo 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;
}
}
}
Puedes usar la clase así:
@Test
void example() {
assertExits(0, () -> System.exit(0)); // succeeds
assertExits(1, () -> System.exit(1)); // succeeds
assertExits(2, () -> System.exit(1)); // fails
}
El código se puede portar fácilmente a JUnit 4, TestNG o cualquier otro marco, si es necesario. El único elemento específico del marco está fallando la prueba. Esto se puede cambiar fácilmente a algo independiente del marco (que no sea un Junit 4 Rule
Hay margen de mejora, por ejemplo, sobrecarga de assertExits()
mensajes personalizables.