Невозможно издеваться над классом URL с помощью PowerMockito/Mockito

Я пытаюсь использовать PowerMockito, чтобы издеваться над созданием класса java.net.URL в моем коде, который я тестирую. По сути, я хочу предотвратить возникновение реального HTTP-запроса и вместо этого 1) проверить данные при выполнении запроса и 2) предоставить свои собственные тестовые данные обратно в издевательском ответе. Вот что я пытаюсь:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ URL.class, MockedHttpConnection.class })
public class Test {
    URL mockedURL = PowerMockito.mock(URL.class);
    MockedHttpConnection mockedConnection = PowerMockito.mock(MockedHttpConnection.class);

...
    PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL);
PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);

...
}

Код, который я хочу протестировать, выглядит так:

URL wlInvokeUrl = new URL(wlInvokeUrlString);
connection = (HttpURLConnection) wlInvokeUrl.openConnection();

Ранее в моем тестовом сценарии я имитировал wlInvokeUrlString, чтобы он соответствовал «MyURLString». Я также пытался использовать различные другие формы строки whenNew, пытаясь внедрить макет. Что бы я ни пытался, это никогда не перехватывает конструктор. Все, что я хочу сделать, это «перехватить» вызов openConnection() и заставить его вернуть мое фиктивное HTTP-соединение вместо реального.

Я издевался над другими классами перед этим в том же скрипте, и они работают, как и ожидалось. Либо мне нужна вторая пара глаз (вероятно, так), либо в классе URL есть что-то уникальное. Я заметил, что если я использую «whenNew(URL.class).withAnyArguments()» и изменю «thenReturn» на «thenAnswer», я могу заставить его срабатывать. Единственная проблема в том, что я никогда не получаю вызов URL для своего кода. То, что я вижу, это вызов конструктора с тремя аргументами для URL.class со всеми нулями для параметров. Может ли быть, что этот класс из среды выполнения Java и загружается программой запуска тестов? Любая помощь горячо приветствуется.


person Jim    schedule 19.02.2016    source источник
comment
вам не нужен Powermock, чтобы проверить это. Просто смоделируйте URL-адрес строки, например, чтобы использовать путь на основе файла (file://my-test.properties). это сработает, и вы сможете передать нужные вам данные   -  person Jérémie B    schedule 19.02.2016
comment
@JérémieB Можете ли вы рассказать об этом подробнее? Это кажется элегантным и простым решением, но тестируемый код ожидает HttpURLConnection, и я получаю ClassCastException, если я изменяю строку URL для ссылки на файл (например, java.lang.ClassCastException: sun.net.www.protocol. ftp.FtpURLConnection нельзя преобразовать в java.net.HttpURLConnection). Кроме того, как бы вы проверили входные параметры запроса, используя этот подход?   -  person Jim    schedule 22.02.2016
comment
Вы не должны использовать напрямую HttpURLConnection, но URL и URLConnection. В HttpURLConnection нет ничего полезного. URL-адрес предназначен для абстрагирования транспортного уровня.   -  person Jérémie B    schedule 22.02.2016


Ответы (3)


Распространенная ошибка при использовании PowerMockito.whenNew.

Обратите внимание, что вы должны подготовить класс, создающий новый экземпляр MyClass для тестирования, а не сам MyClass. Например. если класс, выполняющий new MyClass(), называется X, тогда вам нужно будет сделать @PrepareForTest(X.class) для того, чтобы whenNew работал

Из вики Powermock

Итак, вам нужно немного изменить свой тест и добавить в класс @PrepareForTesta, который создаст новый экземпляр URLlike:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ URL.class, MockedHttpConnection.class , ConnectionUser.class})
public class URLTest {
public class URLTest {

    private ConnectionUser connectionUser;

    @Before
    public void setUp() throws Exception {

        connectionUser = new ConnectionUser();
    }

    @Test
    public void testName() throws Exception {

        URL mockedURL = PowerMockito.mock(URL.class);
        MockedHttpConnection mockedConnection = PowerMockito.mock(MockedHttpConnection.class);

        PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL);
        PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);

        connectionUser.open();

        assertEquals(mockedConnection, connectionUser.getConnection());


    }
}

куда:

public class ConnectionUser {

    private String wlInvokeUrlString = "MyURLString";
    private HttpURLConnection connection;

    public void open() throws IOException {
        URL wlInvokeUrl = new URL(wlInvokeUrlString);
        connection = (HttpURLConnection) wlInvokeUrl.openConnection();
    }

    public HttpURLConnection getConnection() {
        return connection;
    }
}
person Arthur Zagretdinov    schedule 25.02.2016
comment
ваш ответ ближе всего к работе. После внесения этих изменений вызывается конструктор фиктивного URL. Однако он всегда возвращает нулевой объект. Под этим я подразумеваю, что строка URL wlInvokeUrl = new URL(wlInvokeUrlString); всегда присваивает wlInvokeUrl значение null. Я обошел первоначальную проблему, рефакторинг моего кода, но я по-прежнему заинтересован в том, чтобы PowerMockito работал над этим, если у вас есть какие-либо другие предложения. - person Jim; 29.02.2016
comment
Какие версии PowerMock, Mockito, Java вы используете? Я протестировал последнюю версию и Mockito 1.10.19 и могу воспроизвести вашу проблему только в том случае, если я заменю mockedUrlчерез null. Вот так: PowerMockito.whenNew(URL.class).withArguments("MyURLString").thenReturn(null); Во всех остальных случаях мой вариант теста пройден. Кстати, я использую Java 8 для запуска теста. - person Arthur Zagretdinov; 29.02.2016
comment
Не могли бы вы попробовать запустить мою версию в вашей среде? - person Arthur Zagretdinov; 29.02.2016

Я не уверен в разнице между .withParameterTypes(x) и .withArguments(x), но я считаю, что вам нужно настроить его следующим образом, чтобы ваш код работал. Попробуйте и дайте мне знать, если это не поможет.

PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);
PowerMockito.whenNew(URL.class).withArguments(Mockito.anyString()).thenReturn(mockedURL);
person Lencalot    schedule 19.02.2016
comment
Я пробовал эти строки, но я вижу те же результаты. Вместо использования фиктивных объектов создаются и используются реальные классы URL и HttpURLConnection. Может быть проблема в том, что класс URL объявлен как final? Из того, что я прочитал, PowerMockito должен справиться с этим. - person Jim; 22.02.2016

Проблема в том, что аргументы реального вызова не совпадают с ожидаемыми в вашем макете.

PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL) вернет mockedURL только вызов new URL("MyURLString").

Если вы измените его на:

PowerMockito.whenNew( URL.class ).withParameterTypes( String.class )
    .withArguments( org.mockito.Matchers.any( String.class ) ).thenReturn( mockedURL );

Он поймает любую строку, переданную конструктору URL(String) (даже null), и вернет ваш макет.


Когда ты пытался

«whenNew(URL.class).withAnyArguments()» и измените «thenReturn» на «thenAnswer», я мог бы заставить его срабатывать. Единственная проблема в том, что я никогда не получаю вызов URL для своего кода. То, что я вижу, это вызов конструктора с тремя аргументами для URL.class со всеми нулями для параметров.

PowerMock попытается смоделировать все конструкторы (org.powermock.api.mockito.internal.expectation.DelegatingToConstructorsOngoingStubbing.InvokeStubMethod в строке 122), затем вызовет первый (с 3 аргументами) и смоделирует его ответ. Но последующие вызовы вернут уже фиктивный, потому что вы сказали фиктивному для любых аргументов. Вот почему вы видите только один вызов с null, null, null в вашем Answer.

person Willian    schedule 19.02.2016
comment
Я пробовал много вещей, пытаясь заставить эту работу работать, включая вариант вашего предложения. Я повторно протестировал, изменив строку whenNew, как вы упомянули, но все равно получил те же результаты. Любые другие мысли? - person Jim; 22.02.2016