4

I have a largish Maven multiproject build, with unit tests mostly using PowerMock, at version 1.6.2. I decided to integrate JaCoCo, so we could start to publish our code coverage metrics. The latest version of JaCoCo, 0.7.8, requires PowerMock 1.6.6, so I upgraded my version of that.

I'm now seeing some errant behavior with respect to a "whenNew" call that is repeated with variations throughout the tests in the codebase.

The "whenNew()" calls in question look like this:

PowerMockito.whenNew(BlahBlah.class)
            .withArguments(any(SomeClass.class))
            .thenReturn(blahBlahInstance);

With PowerMock 1.6.2, this was working fine. When I upgraded to 1.6.6, in order to make this work, I had to make two changes, one which is acceptable but annoying (because I don't know why it's required), and the other which is really not acceptable.

The acceptable change is to the "@PrepareForTest" annotation at the top of the class. With PowerMock 1.6.2, this used to list only the class where the call to "new BlahBlah(...)" occurs, and that was working fine. In order to get this to work (in addition to the next change), I had to also add "BlahBlah.class" to that list. I can accept this, but it would really help if I had some clear direction on what exactly goes into that list.

The second change that is NOT acceptable is that I had to change ".withArguments(any(SomeClass.class))" to ".withAnyArguments()". I also tried "withArguments(anyObject())", and that also failed to work.

Update:

Note that I'm using version 1.10.19 of mockito-core. I see from https://github.com/powermock/powermock/wiki/MockitoUsage#supported-versions that that version of mockito-core is supported with PowerMock 1.6.6 (1.6.2+).

David M. Karr
  • 14,317
  • 20
  • 94
  • 199

2 Answers2

1

First of all about your statement

The latest version of JaCoCo, 0.7.8, requires PowerMock 1.6.6

JaCoCo doesn't require PowerMock and there is nothing special in JaCoCo 0.7.8 for PowerMock. As of today 0.7.8 is simply latest released version of JaCoCo.

I'm guessing that your statement is kind of misinterpretation that comes from reading of https://github.com/powermock/powermock/wiki/Code-coverage-with-JaCoCo , which states

JaCoCo Offline Instrumentation works only with PowerMock version 1.6.6 and upper.

According to https://github.com/powermock/powermock/blob/master/changelog.txt

Change log 1.6.6 (2016-11-04)

  • Fixed #645 jacoco offline instrumentation incompatibility with powermock byte-code manipulation when using @SuppressStaticInitializationFor

i.e. there was fix just for one particular case. On other cases earlier versions of PowerMock already worked with earlier versions of JaCoCo using offline instrumentation.

Then based on your description of state after upgrade of PowerMock to 1.6.6 , but prior to other changes, trying to create a Minimal, Complete, and Verifiable example :

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>example</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>

    <powermock.version>1.6.6</powermock.version>
    <jacoco.version>0.7.8</jacoco.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>${powermock.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito</artifactId>
      <version>${powermock.version}</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.jacoco</groupId>
      <artifactId>org.jacoco.agent</artifactId>
      <classifier>runtime</classifier>
      <version>${jacoco.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
        <configuration>
          <systemPropertyVariables>
            <jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
          </systemPropertyVariables>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>${jacoco.version}</version>
        <executions>
          <execution>
            <id>default-instrument</id>
            <goals>
              <goal>instrument</goal>
            </goals>
          </execution>
          <execution>
            <id>default-restore-instrumented-classes</id>
            <goals>
              <goal>restore-instrumented-classes</goal>
            </goals>
          </execution>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>report</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

src/main/java/org/example/BlahBlah.java:

package org.example;

public class BlahBlah {

  private final SomeClass c;

  public BlahBlah(SomeClass c) {
    this.c = c;
  }

  public String run() {
    return c.toString();
  }
}

src/main/java/org/example/SomeClass.java:

package org.example;

public class SomeClass {

  private final String s;

  public SomeClass(String s) {
    this.s = s;
  }

  @Override
  public String toString() {
    return s;
  }

}

src/main/java/org/example/Example.java:

package org.example;

public class Example {

  public String fun() {
    return new BlahBlah(new SomeClass("Hello World")).run();
  }

}

src/test/java/org/example/ExampleTest.java:

package org.example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;

@PrepareForTest({Example.class})
@RunWith(PowerMockRunner.class)
public class ExampleTest {

  @Test
  public void test() throws Exception {
    BlahBlah e = new BlahBlah(new SomeClass("Hello PowerMock"));
    PowerMockito.whenNew(BlahBlah.class)
      .withArguments(any(SomeClass.class))
      .thenReturn(e);
    assertEquals("Hello PowerMock", new Example().fun());
  }

}

and

mvn clean verify

works just fine. So there should be something else that was missed in your description to really reproduce your situation. Hope this helps a bit, at least to build better reproducer of based on this.

Additionally: try to decouple process of integration of JaCoCo from upgrade of PowerMock - maybe just this upgrade is problematic and not integration of JaCoCo.

Community
  • 1
  • 1
Godin
  • 9,801
  • 2
  • 39
  • 76
  • If speak honestly, before PowerMock 1.6.6 all classes which added to `@PrepareForTest` had zero coverage due to issue that we discussed in JaCoCo issue. But it also was the issue with `@SuppressStaticInitializationFor`, JaCoCo and TestNG when exception was thrown. – Artur Zagretdinov Feb 05 '17 at 12:10
0

For my specific problem, it turned out that the only places where this wasn't working was when the actual "SomeClass" parameter was null, which wasn't matching "any(SomeClass.class)". In those cases, I had to change the clause to "withAnyArguments()".

David M. Karr
  • 14,317
  • 20
  • 94
  • 199