6

I created a mock HttpsURLConnection based on an StackExchange answer:

import java.net.URL;
import javax.net.ssl.HttpsURLConnection;

...

@RunWith(PowerMockRunner.class)
public class DialogTest {
    public void mockHttpsUrlConnectionExample() throws Exception
    {
        URL mockUrl = PowerMockito.mock(URL.class);
        PowerMockito.whenNew(URL.class).withAnyArguments().thenReturn(mockUrl);
        HttpsURLConnection mockUrlConnection = PowerMockito.mock(HttpsURLConnection.class);
        PowerMockito.when(mockUrl.openConnection()).thenReturn(mockUrlConnection);
        PowerMockito.when(mockUrlConnection.getResponseCode()).thenReturn(200);

        // Create and call my objects ...

    }
}

However, when I use it, I'm seeing a cast exception:

java.lang.ClassCastException: sun.net.www.protocol.https.HttpsURLConnectionImpl cannot be cast to javax.net.ssl.HttpsURLConnection

The problem lies in this code:

import java.net.URL;
import javax.net.ssl.HttpsURLConnection;

...

private Boolean sendRequest(String endpoint, JSONObject requestData, Boolean throwOnAuthException) throws JSONException, IOException {
    this.responseData = null;

    try {
        String serviceURI = getServiceURI();
        String dialogUri = String.format("%s%s", serviceURI, endpoint);
        URL url = new URL(dialogUri);

        // Exception source is this cast
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();

However, when I look at the source code, I see that sun.net.www.protocol.https.HttpsURLConnectionImpl implements javax.net.ssl.HttpsURLConnection

Any suggestions on how to remedy this problem?

Community
  • 1
  • 1
Donal Lafferty
  • 5,807
  • 7
  • 43
  • 60

1 Answers1

8

The issue a conflict between the regular class loader and PowerMock's

The drawback to PowerMock is the use of a custom class loader. This class loader can modify type signatures in a way that is incompatible with the default class loader.

In certain circumstances, instantiation by reflection will cause the default class loader to be used to load a type. That class loader will not know that a type has already be loaded by PowerMock, because of the use of different signatures. The result can be casting errors for objects that should implement the cast type.

To avoid this problem, first stop PowerMock from loading javax.net.ssl.HttpsURLConnection

To prevent the cast exception, use ensure javax.net.ssl.HttpsURLConnection is only loaded by one class loader. Since I cannot stop the regular class loader from being used, the best approach is to stop the PowerMock loader from acting using the @PowerMockIgnore annotation. E.g.

@PowerMockIgnore({"javax.net.ssl.*"})
@PrepareForTest(android.util.Log.class)
public class DialogTest {
...

The side effect is that PowerMock is no longer able to provide it's version of HttpsURLConnection

Next, expose HttpsURLConnection construction, and substitute a mock object

Introduce a factory for HttpsURLConnection. E.g.

public class HttpsUrlConnectionProvider {
    public HttpsURLConnection getHttpsURLConnection(String dialogUri) throws IOException {
        URL url = new URL(dialogUri);
        return (HttpsURLConnection) url.openConnection();
    }
}

Create a mock of the HttpsURLConnection object used for HTTP request E.g.

final HttpsURLConnection mockUrlConnection = PowerMockito.mock(HttpsURLConnection.class);
PowerMockito.when(mockUrlConnection, "getResponseCode").thenReturn(200);
PowerMockito.when(mockUrlConnection, "getOutputStream").thenReturn(outputStream);

// Replace the HttpsURLConnection factory with one that returns our mock HttpsURLConnection
HttpsUrlConnectionProvider mockConnFactory = new HttpsUrlConnectionProvider() {
    public HttpsURLConnection getHttpsURLConnection(String dialogUri) throws
            IOException {
       return mockUrlConnection;
    }
};
dialog.setHttpsUrlConnectionProvider(mockConnFactory);
Donal Lafferty
  • 5,807
  • 7
  • 43
  • 60
  • 3
    I had a somewhat related problem and `@PowerMockIgnore({"javax.net.ssl.*"})` was enough for me :) – mnagel Oct 18 '16 at 11:15