41

Given that Jacoco doesn't play nicely with PowerMockito when instrumenting "on the fly", I've been trying to configure offline instrumentation in the hope this will give me proper unit test coverage for classes that use PowerMockito.

I've setup my pom as below but I still get zero % coverage on my test class. Any help much appreciated as it's driving me slowly bonkers!

<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>mandy</groupId>
    <artifactId>jacoco-test</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>jacoco-test Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <properties>
        <powermock.version>1.5.4</powermock.version>
        <jacoco.version>0.7.1.201405082137</jacoco.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jacoco</groupId>
            <artifactId>org.jacoco.agent</artifactId>
            <classifier>runtime</classifier>
            <version>${jacoco.version}</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>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <executions>
                    <execution>
                        <id>instrument</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>instrument</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>restore-report</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>restore-instrumented-classes</goal>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.17</version>
                <configuration>
                    <!--<argLine>${argLine}</argLine>-->
                    <systemPropertyVariables>
                        <!-- JaCoCo runtime must know where to dump coverage: -->
                        <jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
        </plugins>
        <finalName>jacoco-test</finalName>
    </build>
</project>

here is my class under test:

public class Utils {

    private Utils() {

    }

    public static String say(String s) {
        return "hello:"+s;
    }
}

here is my test:

@RunWith(PowerMockRunner.class)

@PrepareOnlyThisForTest(Utils.class)
@PowerMockIgnore("org.jacoco.agent.rt.*")
public class UtilsTest {

    @Test
    public void testSay() throws Exception {
        PowerMockito.mockStatic(Utils.class);
        Mockito.when(Utils.say(Mockito.anyString())).thenReturn("hello:mandy");
        assertEquals("hello:mandy", Utils.say("sid"));
    }

}

I run mvn clean install which generates the jacoco.exe

Coverage report (generated from jacoco.exec using an ant script ):-

Coverage report

risingTide
  • 1,754
  • 7
  • 31
  • 60
MandyW
  • 1,117
  • 3
  • 14
  • 23

12 Answers12

21

This pom worked for me:

 <build>
    <finalName>final-name</finalName>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <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>0.7.2.201409121644</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>
                    <id>default-report</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

See this link.

gce
  • 1,563
  • 15
  • 17
  • 2
    do you have solution for gradle users? – Bhargav Dec 18 '15 at 21:24
  • 1
    @Bhargav: not really, maybe [this](http://stackoverflow.com/questions/30976319/android-gradle-jacoco-offline-instrumentation-for-integration-tests) answer can help – gce Dec 20 '15 at 17:12
  • 4
    @gce Which PowerMock version were you using? With 1.5.1 and the config above I am getting: `java.lang.NoClassDefFoundError: org.jacoco.agent.rt.internal_e6e56f0.Offline`. Any idea? – codependent Jan 20 '16 at 13:11
  • I managed to solve it but now the problem is that jacoco.exec is never generated: http://stackoverflow.com/questions/34918553/missing-jacoco-exec-file-when-using-jacoco-offline-instrumentation-with-powermoc – codependent Jan 21 '16 at 10:46
  • @gce : Can you please give the full mvn command you used to make it work? Please include all goals run. Are you using all 4 goals of which you have in your pom.xml? – Bradley D Feb 24 '17 at 01:01
  • @BradleyD, I've added full section to the answer, hope that helps. – gce Apr 24 '17 at 20:37
  • @codependent, probably it is too late, but still: org.powermock powermock-module-junit4 1.6.1 test org.powermock powermock-api-mockito 1.6.1 test – gce Apr 24 '17 at 20:44
  • Regardless from the versions, adding "instrument" and "restore-instrumented-classes" steps did the trick. Thanks! – tt_emrah Oct 19 '17 at 10:57
  • @gce, I think it worth mentioning here too that org.jacoco org.jacoco.agent ${jacoco.maven.version} runtime needs to be in the dependency list in the pom.xml. – Artanis Zeratul Aug 31 '21 at 04:38
  • @gce, heaps of thanks to you! You saved me a lot from this post! Kudos to you! – Artanis Zeratul Aug 31 '21 at 05:14
8

I saw the same behavior, though after following the thread on the GitHub issue it seems to be fixed in 1.6.5, which proved true for me.

Hopefully this will save someone a headache later :).

Working configuration with:

  • jacoco-maven-plugin 0.7.7.201606060606
  • powermock 1.6.5

I am not using offline instrumentation.

FatalError
  • 52,695
  • 14
  • 99
  • 116
  • I could also get the report generated as per your comment. I have upgraded the powermock version from 1.5.5 to 1.6.5 and my jacoco-maven-plugin version is 0.7.5.201505241946. Also will post my answer as well – Avinash Reddy Nov 04 '16 at 05:05
  • @SK that code lives at my former employer so I'm afraid I don't have it. Sorry. – FatalError Nov 24 '18 at 21:11
  • It is actually fixed in 1.6.4 itself I believe looking at the changelog - https://github.com/powermock/powermock/blob/powermock-1.6.5/changelog.txt#L26 – Dravidian Sep 14 '21 at 19:28
3

For me this sample of Offline Instrumentation works well.

But it turns in my case that there is a simplier solution: just do not include the tested class in @PrepareForTest({}) annotation before its declaration.

Andriy Boyko
  • 561
  • 7
  • 16
  • 2
    How do we achieve that? I have a class must declared in @PrepareForTest({}), otherwise it will fail. – molly gu Feb 28 '20 at 05:31
2

I made it work using the javaagent of PowerMock. see here: https://github.com/powermock/powermock/wiki/PowerMockAgent

Remove the @RunWith annotations, put the PowerMockRule as described in the link above. Make it public.

Put the following line in the maven-surefire-plugin configuration:

-javaagent:${org.powermock:powermock-module-javaagent:jar}

(used the technique described here : Can I use the path to a Maven dependency as a property?)

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.stackoverflow</groupId>
    <artifactId>q2359872</artifactId>
    <version>2.0-SNAPSHOT</version>
    <name>q2359872</name>

    <properties>
        <!-- Must be listed in the dependencies section otherwise it will be null. -->
        <my.lib>${org.jmockit:jmockit:jar}</my.lib>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.11</version>
        </dependency>
    </dependencies>
    <build>
        <defaultGoal>generate-sources</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>properties</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- Example usage: -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <phase>generate-sources</phase>
                    </execution>
                </executions>
                <configuration>
                    <executable>echo</executable>
                    <arguments>
                        <argument>path to jar=</argument>
                        <argument>${org.jmockit:jmockit:jar}</argument>
                        <argument>my.lib=</argument>
                        <argument>${my.lib}</argument>
                    </arguments>
                </configuration>
            </plugin>
            <!-- end of Example usage -->
        </plugins>
    </build>
</project> 
Michael Fayad
  • 1,216
  • 1
  • 17
  • 38
akhanna3
  • 31
  • 4
2

I used Jacoco offline instrumentation and after execution of test restore ed original classes with help of "restore-instrumented-classes" goal. My JaCoCo configuration look like this:

 <plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.7.9</version>
    <executions>
      <execution>
        <id>default-prepare-agent</id>
        <goals>
          <goal>prepare-agent</goal>
        </goals>
      </execution>
      <execution>
        <id>jacoco-instrument</id>
        <goals>
          <goal>instrument</goal>
        </goals>
      </execution>
      <execution>
        <id>jacoco-restore-instrumented-classes</id>
        <goals>
          <goal>restore-instrumented-classes</goal>
        </goals>
      </execution>
      <execution>
        <id>jacoco-report</id>
        <phase>package</phase>
        <goals>
          <goal>report</goal>
        </goals>
      </execution>
    </executions>
    <configuration>
      <excludes>
        <exclude>*</exclude>
      </excludes>
    </configuration>
  </plugin>
Ananth
  • 787
  • 5
  • 18
1

I ended up using offline instrumentation and Jacoco (similar to what you have done) in conjunction with sonar and I was able to get the coverage numbers from that.

Ashwin Krishnamurthy
  • 3,750
  • 3
  • 27
  • 49
1

I was also facing the same issue. I was able to generate report partially. I have used both these tags for my test cases @RunWith(PowerMockRunner.class) @PrepareForTest({}). And the report was not getting generated for the test cases where i used the above mentioned tags. But for one of the test case there was only this tag @RunWith(PowerMockRunner.class). Somehow the report was getting generated for that case. And also i have never used offline instrumentation. When i tried using the offline instrumentation i was getting error saying that the class was already instrumented. I tried various scenarios and followed various links but could not generate the report. Finally as per the above comment i upgraded my version of powermock from 1.5.5 to 1.6.5 and i was able to generate the report. Following is my pom.xml entry

     <plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.7.5.201505241946</version>
    <executions>
        <execution>
            <id>pre-unit-test</id>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
             <configuration>
                    <!-- Sets the path to the file which contains the execution data. -->
                    <destFile>${basedir}/target/jacoco.exec</destFile>
                    <!--
                        Sets the name of the property containing the settings
                        for JaCoCo runtime agent.
                    -->
            </configuration>
        </execution>
        <execution>
            <!--<id>post-unit-test</id>
            <phase>test</phase>-->
            <id>default-report</id>
            <phase>verify</phase>
            <goals>
                <goal>report</goal>
            </goals>
              <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <dataFile>${basedir}/target/jacoco.exec</dataFile>
                <!-- Sets the output directory for the code coverage report. -->
                <outputDirectory>${basedir}/target/jacoco-ut</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

Following is my entry in pom .xml for maven-surefire-plugin

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
      <configuration>
            <argLine>@{argLine}</argLine>
            <skipTests>false</skipTests>
            <testFailureIgnore>true</testFailureIgnore>
        </configuration>
    </plugin>

@{argLine} was set as a property

<properties>
    <argLine>-noverify -Xms512m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=512m</argLine>
</properties>

And upgraded my powermock version from 1.5.5 to 1.6.5 . Finally i could see my report generation for the classes where i used the following tags in my test cases @RunWith(PowerMockRunner.class) @PrepareForTest({})

Avinash Reddy
  • 2,204
  • 3
  • 25
  • 44
1

Use below maven plugin code snippet this works fine in jenkins as well as in local and shows full code coverage for PowermockRunner unit tests

<build>
            <plugins>
                <plugin>
                    <groupId>org.jacoco</groupId>
                    <artifactId>jacoco-maven-plugin</artifactId>
                    <version>${jacoco-maven-plugin.version}</version>
                    <executions>
                        <execution>
                            <id>pre-test</id>
                            <goals>
                                <goal>prepare-agent</goal>
                            </goals>
                            <configuration>
                                <append>true</append>                           
                            </configuration>
                        </execution>
                        <execution>
                            <id>pre-integ-test</id>
                            <goals>
                                <goal>prepare-agent-integration</goal>
                            </goals>
                            <configuration>
                                <append>true</append>                           
                            </configuration>
                        </execution>

                        <execution>
                            <id>jacoco-instrument</id>
                            <goals>
                                <goal>instrument</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>jacoco-restore-instrumented-classes</id>
                            <goals>
                                <goal>restore-instrumented-classes</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <excludes>
                            <exclude>*</exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
  • I dont know why this answer has been on back burner for so long. This solves the exact issue. Thanks for it. Commenting so that if anyone facing issue using powermock and jacoco instrumentation conflict can benefit from this. – Pankaj Dwivedi Sep 11 '21 at 11:58
1

Code Coverage with JaCoCo explained the reason

The simplest way to use JaCoCo it is — on-the-fly instrumentation with using JaCoCo Java Agent. In this case a class in modified when it is being loaded. You can just run you application with JaCoCo agent and a code coverage is calculated. This way is used by Eclemma and Intellij Idea. But there is a big issue. PowerMock instruments classes also. Javassist is used to modify classes. The main issue is that Javassist reads classes from disk and all JaCoCo changes are disappeared. As result zero code coverage for classes witch are loaded by PowerMock class loader.

We are going to replace Javassist with ByteBuddy (#727) and it should help to resolve this old issue. But right now there is NO WAY TO USE PowerMock with JaCoCo On-the-fly instrumentation. And no workaround to get code coverage in IDE.

JaCoCo Offline Instrumentation can solve this problem. The sample of Offline Instrumentation fixed my problem .

Community
  • 1
  • 1
Robert11
  • 23
  • 5
1

I have tried these versions of the below softwares; they work together and generate the jacoco report without any extra effort. I use the gradle build tool with powermock, mockito and wiremock.

powermock-api-mockito2:2.0.2
powermock-module-junit4:2.0.2
gradle 5.6.2

The above correctly generates the jacoco coverage report.

Das_Geek
  • 2,775
  • 7
  • 20
  • 26
Sunil Kumar
  • 152
  • 1
  • 1
  • 12
  • I'm unsure what you mean by this answer. Did you mean for some of these lines to be commands you type into a terminal? If so, please [edit your answer](https://stackoverflow.com/posts/60099997/edit) to use code-formatting for those lines (used by indenting four spaces or using triple backticks). This post would also benefit from some additional explanation to help provide more context to your answer. These steps will help make your answer more useful and more likely to be upvoted – Das_Geek Feb 06 '20 at 17:05
  • I am sorry, if answer is confusing. what i meant to say is these version of software work together and generate the jacoco report without having to put extra efforts. I have tried it. It is working for me. I am using gradle build tool with powermock, mockito and wiremock. – Sunil Kumar Feb 06 '20 at 17:22
0

I had same issue with JaCoCo On-the-fly and PowerMock. 0% code coverage was generated every time

Found out that JaCoCo version 0.7.7.201606060606 and PowerMock version 1.6.2 are compatible and code coverage is generated successfully.

Zilvinas
  • 1
  • 1
0

The report is correct. The provided test leads to no coverage as you mock what happens when calling the method "say". One should never mock the "instance/class under test", only do this for the dependencies and surroundings of the test subject.

In this case: The code return "hello:"+s; is never reached by the test and the constructor of Utils is not called. There is no coverage displayed as there is no test coverage of these lines at all.

Nevertheless using JaCoCo and PowerMock together is not trivial. I am currently looking for how to get it to run, too. Maybe your provided test is just unluckily written and JaCoCo is already working as it should?

Philipp
  • 96
  • 2
  • 5