7

I am trying to run powermock + mockito with Java 11 for unit test cases. I am using the below versions:

testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.28.2'
testCompile group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.2'
testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.2'

After a lot of trial and error I got the test to start executing with Java 11 but not able to run the tests which have a static block with Java 11 http client. I added the

@PowerMockIgnore({"javax.management.*", "sun.security.ssl.*", "javax.net.ssl.*", "java.net.http.*", "jdk.internal.net.http.*"})

but still not able to get it to work. The exception is

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make public java.net.http.HttpClient$Builder jdk.internal.net.http.HttpClientBuilderImpl.priority(int) accessible: module java.net.http does not "exports jdk.internal.net.http" to unnamed module @548b7f67

Here's the full stack strace:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.powermock.reflect.internal.WhiteboxImpl (file:/Users/subhomoysikdar/.gradle/caches/modules-2/files-2.1/org.powermock/powermock-reflect/2.0.2/79df0e5792fba38278b90f9e22617f5684313017/powermock-reflect-2.0.2.jar) to method java.lang.Object.clone()
WARNING: Please consider reporting this to the maintainers of org.powermock.reflect.internal.WhiteboxImpl
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

java.lang.ExceptionInInitializerError
    at jdk.internal.reflect.GeneratedSerializationConstructorAccessor4.newInstance(Unknown Source)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
    at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
    at org.mockito.internal.creation.instance.ObjenesisInstantiator.newInstance(ObjenesisInstantiator.java:19)
    at org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.createMock(SubclassByteBuddyMockMaker.java:47)
    at org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker.createMock(ByteBuddyMockMaker.java:25)
    at org.powermock.api.mockito.mockmaker.PowerMockMaker.createMock(PowerMockMaker.java:41)
    at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:35)
    at org.mockito.internal.MockitoCore.mock(MockitoCore.java:62)
    at org.mockito.Mockito.mock(Mockito.java:1908)
    at org.powermock.api.mockito.internal.mockcreation.DefaultMockCreator.createMethodInvocationControl(DefaultMockCreator.java:108)
    at org.powermock.api.mockito.internal.mockcreation.DefaultMockCreator.doCreateMock(DefaultMockCreator.java:61)
    at org.powermock.api.mockito.internal.mockcreation.DefaultMockCreator.createMock(DefaultMockCreator.java:53)
    at org.powermock.api.mockito.internal.mockcreation.DefaultMockCreator.mock(DefaultMockCreator.java:40)
    at org.powermock.api.mockito.PowerMockito.mockStatic(PowerMockito.java:62)
    at com.vmware.hcs.broker.broker.catalogue.util.TenantCacheTest.test(TenantCacheTest.java:29)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:326)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:310)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.access$100(PowerMockJUnit47RunnerDelegateImpl.java:59)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner$TestExecutorStatement.evaluate(PowerMockJUnit47RunnerDelegateImpl.java:147)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.evaluateStatement(PowerMockJUnit47RunnerDelegateImpl.java:107)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:298)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:218)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:160)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:134)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:136)
    at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:117)
    at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:57)
    at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.RuntimeException: java.lang.reflect.InaccessibleObjectException: Unable to make public java.net.http.HttpClient$Builder jdk.internal.net.http.HttpClientBuilderImpl.priority(int) accessible: module java.net.http does not "exports jdk.internal.net.http" to unnamed module @548b7f67
    at com.ConfigServiceRestClient.<clinit>(ConfigServiceRestClient.java:68)
    ... 48 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make public java.net.http.HttpClient$Builder jdk.internal.net.http.HttpClientBuilderImpl.priority(int) accessible: module java.net.http does not "exports jdk.internal.net.http" to unnamed module @548b7f67
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:340)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:280)
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:198)
    at java.base/java.lang.reflect.Method.setAccessible(Method.java:192)
    at org.powermock.reflect.internal.WhiteboxImpl.doGetAllMethods(WhiteboxImpl.java:1499)
    at org.powermock.reflect.internal.WhiteboxImpl.getAllMethods(WhiteboxImpl.java:1473)
    at org.powermock.reflect.internal.WhiteboxImpl.getMethods(WhiteboxImpl.java:1741)
    at org.powermock.reflect.internal.WhiteboxImpl.getMethods(WhiteboxImpl.java:1780)
    at org.powermock.reflect.internal.WhiteboxImpl.getBestMethodCandidate(WhiteboxImpl.java:999)
    at org.powermock.core.MockInvocation.findMethodToInvoke(MockInvocation.java:58)
    at org.powermock.core.MockInvocation.init(MockInvocation.java:35)
    at org.powermock.core.MockInvocation.<init>(MockInvocation.java:22)
    at org.powermock.core.MockGateway.doMethodCall(MockGateway.java:155)
    at org.powermock.core.MockGateway.methodCall(MockGateway.java:138)
    at com.ConfigServiceRestClient.<clinit>(ConfigServiceRestClient.java:62)
    ... 48 more
Subhomoy Sikdar
  • 526
  • 5
  • 19
  • 3
    The usual hint for powermock users: don't use powermock. Unless you have to test old legacy code you cant change ... simply learn how to write easy-to-test code. Such code you can test easily with the basic features of Mockito. PowerMock(ito) is nothing but a big hammer to fight symptoms of hard-to-test code. Invest your time in getting rid of it, instead of moving it to the next generation ;-) – GhostCat Jun 21 '19 at 08:55
  • @GhostCat But say we have some class, the ConfigServiceRestClient in this case, which is marked final (as it should not be extended); how do you suggest to test such code? – Subhomoy Sikdar Jun 21 '19 at 09:01
  • For example by having the **core** methods of that class ... being defined on an interface. If that class is so important to make it final ... then for sure it would be helpful to do your best to hide the implementation, and to have all your client code deal with interfaces?! And then only *one* place in your code needs to know the actual class, and you use dependency injection to provide mocked interfaces when testing. – GhostCat Jun 21 '19 at 09:03
  • 2
    What I am basically saying is: we stopped using PowerMock(ito) some years back. And even with Mockito, we almost always only use the base function it provides. It is very rare that we use spies, or argument captors for example. Simple straight forward production code leads to simple, straight forward tests. Guess what: since we stopped using PowerMock, our production code became better. You see, when your code is hard to unit test (and you need PowerMock to do that), it is probably also hard to test overall. Even when doing func/integration testing. – GhostCat Jun 21 '19 at 09:05

2 Answers2

2

This would have been a lot easier to answer if you would actually show where you create the HttpClient, but I do assume it is created somehow like this?

class SomeClass {

      private static final HttpClient CLIENT;

      static {
          CLIENT = HttpClient.newBuilder()
                             ...
                             ...
                             .build();
      }
}

In such a case there are a few things you need in your test class:

@RunWith(PowerMockRunner.class)
@PrepareForTest(value = SomeClass.class)
@PowerMockIgnore({"javax.net.ssl.*"})
@SuppressStaticInitializationFor({"SomeClass"}) // fully qualified name in here
    
class SomeClassTest {

     @Before
     public void before(){
         Whitebox.setInternalState(SomeClass.class, "CLIENT", (HttpClient) null);
     }
}

The key point is that you need to "disable" static blocks via:

@SuppressStaticInitializationFor.

When you use the annotation : @PrepareForTest, ultimately, Mockito will try to find the methods of HttpClientBuilderImpl (that in turn is the result of HttpClient.newBuilder(), that in turn is called from static {} blocks that you have).

The modularization of java client means that jdk.internal.net.http.HttpClientBuilderImpl is not "exported", so no one can really use that (even if it is public). Mockito, internally, tries to find all methods of HttpClientBuilderImpl and see if it can access them. In your case, it has seen that HttpClientBuilderImpl.priority(int) is not such a method and it failed.

Eugene
  • 117,005
  • 15
  • 201
  • 306
0

I ran into this today, and will share my solution in hopes it may help. A few things to keep in mind:

  1. I'm using Spring Boot, but t should be a similar approach if you're not.
  2. My service takes an HttpClient parameter to allow for passing a mock in.
// The interface
public interface TestService {
    String myEndpoint();
}

// The service that uses a Java 11 HTTP Client
public class TestServiceImpl implements TestService {
    private final HttpClient client;

    public TestServiceImpl(HttpClient client) {
        this.client = client;
    }

    @Override
    public String myEndpoint() {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(
                        UriComponentsBuilder.fromHttpUrl("https://service.com")
                                .path("/the-endpoint/")
                                .build()
                                .toUri()
                )
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
        try {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            return response.body();
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException("uh oh", e);
        }
    }
}

// Test with mocks
@ExtendWith(SpringExtension.class)
public class TestServiceTest {
    @MockBean
    private HttpClient httpClient;
    private TestService testService;

    @BeforeEach
    public void before() {
        testService = new TestServiceImpl(httpClient);
    }

    @Test
    public void testSomething() throws IOException, InterruptedException {
        byte[] testBytes = "pdfBytes".getBytes();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(
                        UriComponentsBuilder.fromHttpUrl("https://service.com")
                                .path("/the-endpoint/")
                                .build()
                                .toUri()
                )
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();

        // You could make a utility class with some static methods to help you build these
        // and optionally set things like status for various test cases.
        HttpResponse<String> response = new HttpResponse<>() {
            @Override
            public int statusCode() {
                return 200;
            }

            @Override
            public HttpRequest request() {
                return null;
            }

            @Override
            public Optional<HttpResponse<String>> previousResponse() {
                return Optional.empty();
            }

            @Override
            public java.net.http.HttpHeaders headers() {
                return null;
            }

            @Override
            public String body() {
                return "myContent";
            }

            @Override
            public Optional<SSLSession> sslSession() {
                return Optional.empty();
            }

            @Override
            public URI uri() {
                return null;
            }

            @Override
            public HttpClient.Version version() {
                return HttpClient.Version.HTTP_2;
            }
        };

        given(httpClient.send(request, HttpResponse.BodyHandlers.ofString()))
                .willReturn(response);

        assertThat(testService.myEndpoint()).isEqualTo("myContent");
    }
}
PCalouche
  • 1,605
  • 2
  • 16
  • 19