Subhomoy Sikdar
Subhomoy Sikdar

Reputation: 674

Running powermock + mockito on java 11 http client

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

Upvotes: 8

Views: 10793

Answers (2)

Hellfish80
Hellfish80

Reputation: 1645

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");
    }
}

Upvotes: 0

Eugene
Eugene

Reputation: 120858

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.

Upvotes: 2

Related Questions