PowerMockito.whenNew mocks для анонимного внутреннего класса [дубликат]

A + соответствует одному или нескольким экземплярам предыдущего шаблона. * соответствует нулю или более экземплярам предыдущего шаблона.

Итак, в основном, если вы используете +, должен быть хотя бы один экземпляр шаблона, если вы используете *, он будет по-прежнему совпадают, если нет экземпляров.

7
задан Eric Alberson 4 April 2016 в 12:40
поделиться

4 ответа

Сообщение довольно очевидно: вы не можете высмеивать невидимые и окончательные классы. Короткий ответ: создайте именованный класс вашего анонимного, и вместо этого протестируйте этот класс!

Длинный ответ, давайте разозлим почему!

Анонимный класс является окончательным

Вы создаете анонимный класс FilterFactory, когда компилятор видит анонимный класс, он создает конечный и видимый класс пакета. Таким образом, анонимный класс не является макетным по стандартным средним значениям, то есть через Mockito.

Mocking anonymous class: возможно, но BRITTLE, если не HACKY

OK, теперь предположите, что вы хотите быть в состоянии издеваться над этим анонимный класс через Powermock. Текущие компиляторы компилируют анонимный класс со следующей схемой:

Declaring class + $ + <order of declaration starting with 1>

Mocking анонимный класс возможен, но хрупкий (и я имею в виду это). Предположим, что анонимный класс является одиннадцатым, который будет объявлен, он появится как

InputHelper$11.class

Итак, вы могли бы подготовиться к тестированию анонимного класса:

@RunWith(PowerMockRunner.class)
@PrepareForTest({InputHelper$11.class})
public class InputHelperTest {
    @Test
    public void anonymous_class_mocking works() throws Throwable {
        PowerMockito.spy(InputHelper.BZIP2_FACTORY);  // This line fails
    }
}

Этот код будет компилироваться, НО в конечном итоге будет сообщен как ошибка с вашей IDE. Вероятно, IDE не знает о InputHelper$11.class. IntelliJ, который не использует скомпилированный класс для проверки отчета о коде.

Также тот факт, что анонимное имя класса фактически зависит от порядка объявления, является проблемой, когда кто-то добавляет еще один анонимный класс раньше, нумерация может измениться. Анонимные классы сделаны для того, чтобы оставаться анонимными, что, если ребята-компиляторы однажды решат использовать буквы или даже случайные идентификаторы!

Так насмешливые анонимные классы через Powermock возможны, но хрупкие, никогда не делайте этого в real project!

EDITED ПРИМЕЧАНИЕ: Компилятор Eclipse имеет другую схему нумерации, он всегда использует 3-значное число:

Declaring class + $ + <pad with 0> + <order of declaration starting with 1>

Также я не думаю, что JLS четко укажет как компиляторы должны назвать анонимные классы.

Вы не переназначаете шпиона в статическое поле

PowerMockito.spy(InputHelper.BZIP2_FACTORY);  // This line fails
whenNew(BufferedInputStream.class).withArguments(in).thenReturn(buffer);
whenNew(CBZip2InputStream.class).withArguments(buffer).thenReturn(expected);
InputStream observed = InputHelper.BZIP2_FACTORY.makeFilter(in);

PowerMockito.spy возвращает шпиона, он не меняет значение из InputHelper.BZIP2_FACTORY. Таким образом, вам нужно будет установить через отражение это поле. Вы можете использовать утилиту Whitebox, которую предоставляет Powermock.

Заключение

Слишком много проблем, чтобы просто проверить с помощью mocks, что анонимный фильтр использует BufferedInputStream.

Альтернатива

Я бы скорее написал следующий код:

Помощник ввода, который будет использовать именованный класс, я не использую имя интерфейса, чтобы сообщить пользователю, что

public class InputHelper {
    public static final BufferedBZIP2FilterFactory BZIP2_FACTORY = new BufferedBZIP2FilterFactory();
}

И теперь сам фильтр:

public class BufferedBZIP2FilterFactory {
    public InputStream makeFilter(InputStream in) {
        BufferedInputStream buffer = new BufferedInputStream(in);
        return new CBZip2InputStream(buffer);
    }
}

Теперь вы можете написать тест следующим образом:

@RunWith(PowerMockRunner.class)
public class BufferedBZIP2FilterFactoryTest {

    @Test
    @PrepareForTest({BufferedBZIP2FilterFactory.class})
    public void wraps_InputStream_in_BufferedInputStream() throws Exception {
        whenNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class))
                .thenReturn(Mockito.mock(CBZip2InputStream.class));

        new BufferedBZIP2FilterFactory().makeFilter(anInputStream());

        verifyNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class));
    }

    private ByteArrayInputStream anInputStream() {
        return new ByteArrayInputStream(new byte[10]);
    }
}

Но в конечном итоге это может привести к тому, что для этого тестового сценария не удастся использовать материал powermock, если вы вынудите CBZip2InputStream принять только BufferedInputStream. Обычно использование Powermock означает, что с дизайном что-то не так. По моему мнению, Powermock отлично подходит для устаревших программных продуктов, но может скрывать разработчиков при разработке нового кода; поскольку они упускают смысл хорошей части ООП, я бы даже сказал, что они разрабатывают устаревший код.

Надеюсь, что это поможет!

12
ответ дан Brice 17 August 2018 в 09:33
поделиться
  • 1
    +1 для внутреннего класса $ 1 :) Я подумал об этом, но я не осмелился попробовать его. С другой стороны, для коротких классов я предпочел бы использовать анонимный класс и класс $ 1. Его просто уродливо назвать класс только для тестирования. – Gábor Lipták 6 March 2012 в 23:52
  • 2
    Да, я согласен, но в этом случае анонимный класс довольно прост и не нуждается в такой сильной настройке тестирования. В случае Зака ​​кажется, что он имеет дело с большим количеством кода во внутреннем классе. В конце концов, это зависит от критичности тестируемого, если оно считается важным, тогда сам код должен быть заметным. – Brice 7 March 2012 в 13:18
  • 3
    Благодаря вам я понимаю, что я упускаю некоторые важные знания. Я использую класс utils (или вспомогательный), чтобы уменьшить код для создания и тестирования абстрактного класса, и, конечно, я хочу, чтобы их шпионят. Это была ошибка, поэтому теперь я создаю простой класс расширения в тестовых источниках и шпионирую это вместо этого и его нормально – Perlos 4 December 2012 в 08:10

Старый пост, но вам не нужно создавать именованный класс - вместо этого используйте подстановочные знаки, как упоминалось в этом сообщении . Мощный конструктор powermock через whennew () не работает с анонимным классом

@PrepareForTest(fullyQualifiedNames = "com.yourpackage.containing.anonclass.*")

6
ответ дан Community 17 August 2018 в 09:33
поделиться
  • 1
    Это правильный ответ. Но если вы PowerMocking индивидуальный класс, он может выглядеть как @PrepareForTest(fullyQualifiedNames = "com.yourpackage.YourClass*") – gladed 23 September 2016 в 20:25

Я только что столкнулся с той же проблемой. Итак, согласно документации конструктора, издевающегося над , вам нужно подготовить класс, который создаст класс зла (ы). В вашем случае злые классы BufferedInputStream и CBZip2InputStream, а их создатель - анонимный класс, который не может быть определен в аннотации PrepareForTest. Поэтому я должен был сделать то же самое, что и вы (хм, просто увидел ваш комментарий), я переместил анонимный класс в названный класс.

0
ответ дан Gábor Lipták 17 August 2018 в 09:33
поделиться

Вам нужно запустить тест с помощью Runner PowerMockito, и вам нужно указать структуру, у которых класс (ы) должен иметь пользовательское поведение. Добавьте следующие классные аннотации к вашему тестовому классу:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ BufferedInputStream.class })
0
ответ дан LaurentG 17 August 2018 в 09:33
поделиться
  • 1
    Аннотации не являются проблемой. Если я изменю InputHelper.BZIP2_FACTORY от анонимного внутреннего класса до именованного внутреннего класса, то он будет работать. – Zack 6 November 2011 в 15:29
Другие вопросы по тегам:

Похожие вопросы: