Testen von Exceptions mit JMockit

Für einen Unit-Test benötigt man meistens nur gewisse Teile eines "fremden" Objekts. Oft genügt einem eine fixe Anzahl definierter Rückgabewerte. Statt das man nun das "fremde" Objekt erzeugt und die Werte aus einer Datenbank holt, setzt man eine Testattrappe ein. Diese Attrappe liefert einem die definierten Rückgabewerte oder Exceptions, ohne dabei wirklich mit der Datenbank, dem Dateisystem oder dem Netzwerk zu sprechen. So eine Attrappe kann ein Mock oder Stub sein.

JMockit ist ein Framework für Java, das einem bei der Erstellung von Mocks und Stubs hilft. Für Java gibt es zahlreiche solcher Frameworks. JMockit hat aber einige besondere Fähigkeiten, was das Testen deutlich erleichtern kann.

Für AtaraxiS war ich in den letzten Tagen am erweitern der Unit-Tests. Die Testabdeckung war noch nicht überall so gut, wie ich diese haben will. Es fehlten vor allem noch die zahlreichen Catch-Blocke für die Exceptions.
(Wieso überhaupt Exceptions testen? AtaraxiS will die Verschlüsselung abstrahieren und reduziert die dabei möglichen Exceptions auf eine handlichere Anzahl. Diese Tests dienen der Kontrolle, ob auch nach dem anstehenden Umbau noch alles wie gewohnt läuft.)

public ACDecryptInputStream (File inFile, SecretKey aesKey) throws IOException
{
    try
    {
        // Parse the Header of the File
        AtaraxisHeaderParser ahParser = new AtaraxisHeaderParser(inFile);

        // When it contains a Header, then prepare all for CBC-Mode
        IvParameterSpec ivSpec = new IvParameterSpec(ahParser.getIV());

        fis = new FileInputStream(inFile);
        fis.skip(ahParser.bytesToSkip());

        cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
        cipher.init(Cipher.DECRYPT_MODE, aesKey, ivSpec);

        // Create the CipherInputStream
        decryptedInputStream = new CipherInputStream(fis, cipher);
    } 
    catch (NoSuchAlgorithmException e)
    {
        LOGGER.error("NoSuchAlgorithmException", e);
        throw new IOException("NoSuchAlgorithmException");
    } 
    .....
}

Um dies zu testen, müsste die Klasse Cipher bei getInstance() eine NoSuchAlgorithmException werfen. Dies könnte ich durch eine Abstraktionsschicht zwischen unserem Code und dem von BouncyCastle machen. Das bedingt aber eine Änderung am Code. Hier wäre das zwar möglich, doch geht das nicht immer.

JMockit hat dafür eine passende Methode. Mittels Mockit.redefineMethods() kann eine Methode umgeleitet werden. Das angegebene Ziel kann genau das machen, was man von ihm erwartet. Um die Exception zu werfen, dient dieser Aufruf:

@Test(expected=IOException.class)
public void ACDecryptInputStream_NoSuchAlgorithmException() throws Exception
{
    Mockit.redefineMethods(javax.crypto.Cipher.class, new Object() {
        @SuppressWarnings("unused")
        public final Cipher getInstance(String transformation,
                Provider provider)
         throws NoSuchAlgorithmException,
                NoSuchPaddingException
        {
            throw new NoSuchAlgorithmException();
        }
    });

    ACDecryptInputStream acI = new ACDecryptInputStream(testFile, key);
    fail("IOException missing for " + acI);
}

Dies ist nur eine der unzähligen Möglichkeiten, die JMockit bietet. Nach dem gleichen Prinzip kann man beliebige Rückgabewerte zurückliefern. Wer mehr dazu wissen will, findet auf der Projektseite von JMockit eine sehr detaillierte Anleitung.