Wer eine gewisse Zeit mit Powermock arbeitet, wird irgendwann auf folgende Problemsituation stoßen.

Nehmen wir an, unser zu testender Code sieht wie folgt aus:

private int returnResponseCode(String url) throws Exception {
   URL obj = new URL(url);
   HttpURLConnection con = (HttpURLConnection) obj.openConnection();
   ...
   int responseCode = con.getResponseCode();
   return responseCode;
}

Es wird also in der markierten Zeile ein neues Objekt erzeugt und dann im Folgenden verwendet, aber NICHT zurückgegeben.

Im Test werden wir also die Erstellung dieses Objekts mocken müssen. Dazu verwendet man PowerMock. Eine allgemeine Beschreibung von PowerMock findet sich hier.

Der Testcode könnte wie folgt aussehen:

...
mockURL = PowerMockito.mock(URL.class);
PowerMockito.whenNew(URL.class).withArguments(url).thenReturn(mockURL);
mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection);
...

In den markierten Zeilen wird PowerMock benutzt und deshalb müssen wir einerseits den PowerMockRunner verwenden und andererseits die Klasse, die das new aufruft für Powermock vorbereiten:


@RunWith(PowerMockRunner.class)
@PrepareForTest({ MyClass.class, URL.class })
public class MyClassTest {
...
}

Doch genau darin besteht das Problem.

Wenn die vorzubereitende Klasse nämlich genau die ist, die getestet wird, ist das JaCoCo Maven Plugin von EclEmma (oder Sonar) nicht mehr in der Lage die Code Coverage dieser Klasse zu bestimmen.

Die ermittelte Code Coverage liegt bei 0%, was ja nicht ganz der Wahrheit entspricht.

Alternativ zu der @RunWith Deklaration könnte man jedoch mit sogenannten JUnit Rules arbeiten. Es gibt nämlich auch für Powermock eine solche PowermockRule:

@PrepareForTest({ MyClass.class })
public class MyClassTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();
    ...
}

Wie genau die PowerMockRule in Projekten eingebunden werden kann, findet sich hier.

Die Checkliste für Maven-JUnit-basierte Projekte ist simpel:

  1. Füge die Dependency hinzu:
    <dependency>
       <groupId>org.powermock</groupId>
       <artifactId>powermock-module-junit4-rule-agent</artifactId>
       <version>LATEST</version>
       <scope>test</scope>
    </dependency>
    
  2. Stelle sicher, dass diese Dependency VOR JUnit geladen wird.
  3. Importiere die Rule:
    import org.powermock.modules.junit4.rule.PowerMockRule;
    

In einigen Fällen funktioniert diese Lösung leider nicht direkt, weil beim Ausführen des Tests folgender Fehler geworfen wird:


java.lang.VerifyError: Expecting a stackmap frame at branch target ***
Exception Details:
Location:
...
Reason:
Expected stackmap frame at this location.
Bytecode:
0x0000000: 2a2b b600 2c4d 2cc1 002e 9900 0b2c c000
0x0000010: 2eb6 0031 b001 b0

at java.lang.Class.getDeclaredConstructors0(Native Method)
...
at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:63)
...
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)

Je nach JDK Version gibt es nun unterschiedliche Möglichkeiten, diesen Fehler zu verhindern:

  • JDK 7 JVM-Option: -XX:UseSplitVerifier
  • JDK 8 JVM-Option: -Xverify:none

Es scheint ein Problem mit der ByteCode Manipulation durch ASM (javassist-3.8.1) zu geben.

No coverage – use PowerMockAgent
Markiert in:                 

5 Kommentare zu „No coverage – use PowerMockAgent

  • Pingback:Spys like us | Michael Albrecht

  • 3. Juni 2015 um 19:09
    Permalink

    Danke für den kleinen Artikel. Zwei Hinweise dazu:

    1. Die JVM8-Option heisst -Xverify:none

    2. Die PowerMockAgents scheinen subtil anders als der PowermockRunner zu arbeiten:
    Wenn die statisch gemockte MyClass als final markiert wurde, dann streichen die PowerMockAgents die Segel. Der PowerMockRunner schafft das irgendwie. (Magic I believe.)

    Antworten
    • 9. Juli 2015 um 10:40
      Permalink

      Ad 1. Habe ich geändert. Danke.

      Antworten
  • 23. August 2016 um 19:17
    Permalink

    Hallo.
    Ist das eine command line option? Ich arbeite unter InteliJ, und wenn ich unter additional command line parameters diese -XX:UseSplitVerifier eintrage, bekomme ich weiterhin den oben beschriebenen Error.
    Wo soll die Option eingestellt werden?
    Danke

    Antworten
    • 29. August 2016 um 9:34
      Permalink

      Diese Option ist eine der Java Virtual Machine. Ich bin jetzt ein (pazifistischer) IntelliJ-Nicht-Benutzer 🙂 und weiß nicht genau, wo Du die Einstellungen vornehmen musst, aber wenn man die JavaVM damit startet, fällt dieser Fehler weg.
      Ich hoffe damit geholfen zu haben.

      Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.