Using PowerMock in JDK 16
Asked Answered
L

2

6

Java reflection is becoming more and more restricted:

  • Up to Java 8 all operations are allowed
  • Starting from Java 9 to 15 you are still able to perform the operations, but you will receive a warning
  • From Java 16 and onwards the operations are forbidden between modules (well, still possible with some special arguments passed to the JVM)

This is a serious problem when using libraries that rely heavily on reflection, like PowerMock that uses it to mock objects in tests.

I created this simple example that illustrates the issue. Here is the pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Powermock</name>

    <properties>
        <java.version>16</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <parameters>true</parameters>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

With this simple test SomeTest.java:

package com.example.demo;

import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
class SomeTest {
    @Test
    void contextLoads() {
        Assert.assertEquals(1, 1);
    }
}

Then if we run the command mvn clean test we will get the error:

Running com.example.demo.SomeTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.341 sec <<< FAILURE!
initializationError(com.example.demo.SomeTest)  Time elapsed: 0 sec  <<< ERROR!
java.lang.RuntimeException: java.lang.reflect.InaccessibleObjectException: Unable to make protected native
    java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException accessible:
    module java.base does not "opens java.lang" to unnamed module @8b96fde

Strangely if we add this bit to the pom.xml, the problem goes away:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
    </parent>

Anybody that can help me with this:

  • How to use PowerMock in JDK 16?
  • Is even PowerMock supported in JDK 16?
  • Is this issue solved using Java modules?
  • Why adding Spring Boot as parent solves the problem?
Landahl answered 22/6, 2021 at 17:50 Comment(6)
My first question would be: Why do you need powermock? Why not using Mockito?Armet
I'm using PowerMock to be able to mock final, static or private methods.Landahl
That's what I expected. But now the question is: Why do you need to mock "final" or "static" or much more worse "private" methods... That sounds like having a lot of code problems. Final things can be mocked with Mockito as well. But really which kind of static methods do you need to mock? In particular in relationship with Spring....Apart from that you are mixing JUnit Jupiter (aka JUnit 5) with JUnit 4 apart from that Mockito has a "mockStatic" (see #61975800)Armet
Actually I switched to Mockito and almost everything worked. For the cases it didn't I was able to write the test in a different way. Thanks a lot for the tip!Landahl
@Armet Why mock "final" and "static" methods sounds like having a lot of code problems? Why shouldn't we mock them?Florist
Based on the usage of Powermock because it's most of the time use in cases where people need to mock static/final parts. That's often a hint about issues in code...If you don't do such things... Mockito is usually a more easier and more flexible solution also related to JUnit 5 etc.Armet
J
1

Use This plugin in POM file:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.base=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
Jaclynjaco answered 20/4, 2022 at 7:55 Comment(0)
I
0

For Java 17 and above, I use this in my POM (tested up to Java 20):

           <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
                <!--suppress MavenModelInspection -->
                <configuration>
                    <argLine>
                        ${argLine}
                        --add-opens java.base/java.lang=ALL-UNNAMED
                        --add-opens java.base/java.math=ALL-UNNAMED
                        --add-opens java.base/java.io=ALL-UNNAMED
                        --add-opens java.base/java.util=ALL-UNNAMED
                        --add-opens
                        java.base/java.util.stream=ALL-UNNAMED
                        --add-opens java.base/java.text=ALL-UNNAMED
                        --add-opens
                        java.base/java.util.regex=ALL-UNNAMED
                        --add-opens
                        java.base/java.nio.channels.spi=ALL-UNNAMED
                        --add-opens java.base/sun.nio.ch=ALL-UNNAMED
                        --add-opens java.base/java.net=ALL-UNNAMED
                        --add-opens
                        java.base/java.util.concurrent=ALL-UNNAMED
                        --add-opens java.base/sun.nio.fs=ALL-UNNAMED
                        --add-opens java.base/sun.nio.cs=ALL-UNNAMED
                        --add-opens java.base/java.nio.file=ALL-UNNAMED
                        --add-opens
                        java.base/java.nio.charset=ALL-UNNAMED
                        --add-opens
                        java.base/java.lang.reflect=ALL-UNNAMED
                        --add-opens
                        java.logging/java.util.logging=ALL-UNNAMED
                        --add-opens java.base/java.lang.ref=ALL-UNNAMED
                        --add-opens java.base/java.util.jar=ALL-UNNAMED
                        --add-opens java.base/java.util.zip=ALL-UNNAMED
                    </argLine>
                </configuration>
            </plugin>

In addition, if you are using Eclipse and want to run JUnit tests in the IDE, it will fail unless you tell it about the --add-opens above. To do that go to Preferences->Java->Installed JREs and select your active JRE/JDK and click Edit. Then in the Default VM Arguments box copy and paste all of the --add-opens (do not include the {$argLine} reference). Once you have done that, you should be able to run the JUnit tests without any errors.

Old Answer of Java 16 only: You can use PowerMockito with Java 16 by using the --illegal-access=permit option in the Maven surefire plugin. You will see warnings, but it works.

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
                <configuration>
                    <argLine>
                        --illegal-access=permit
                    </argLine>
                </configuration>
            </plugin>

For reference this is what I use for Mockito and PowerMockito:

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.11.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>
Infantile answered 11/8, 2021 at 5:42 Comment(1)
--illegal-access=permit doesn't work anymore in Java 17. The problem with Java 16 and 17 has an issue in Powermock's project on Github: github.com/powermock/powermock/issues/1099Manic

© 2022 - 2024 — McMap. All rights reserved.