Как использовать Powermockito для создания новых объектов при тестировании метода в анонимном классе?

Я хотел бы написать тест JUnit, чтобы убедиться, что приведенный ниже код использует BufferedInputStream:

public static final FilterFactory BZIP2_FACTORY = new FilterFactory() {
    public InputStream makeFilter(InputStream in) {        
        // a lot of other code removed for clarity 
        BufferedInputStream buffer = new BufferedInputStream(in);
        return new CBZip2InputStream(buffer);
    }
};

(FilterFactory-это интерфейс.)

мой тест до сих пор выглядит так:

@Test
public void testBZIP2_FactoryUsesBufferedInputStream() throws Throwable {
    InputStream in = mock(InputStream.class);
    BufferedInputStream buffer = mock(BufferedInputStream.class);
    CBZip2InputStream expected = mock(CBZip2InputStream.class);

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

    assertEquals(expected, observed);
}

вызов PowerMockito.spy вызывает исключение с этим сообщением:

org.mockito.exceptions.base.MockitoException: 
Mockito cannot mock this class: class edu.gvsu.cis.kurmasz.io.InputHelper
Mockito can only mock visible & non-final classes.

что я должен использовать вместо PowerMocktio.шпион, чтобы настроить звонки на whenNew?

4 ответов


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

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

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

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

насмешливый анонимный класс: возможно, но хрупкий, если не HACKY

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

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

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

InputHelper.class

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

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

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

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

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

ОТРЕДАКТИРОВАННОЕ ПРИМЕЧАНИЕ: компилятор 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.

вывод

слишком много проблем, чтобы просто проверить с насмешками, что анонимный фильтр использует 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 отлично подходит для устаревшего программного обеспечения, но может ослепить разработчиков при разработке нового кода; поскольку им не хватает хорошей части ООП, я бы даже сказал, что они разрабатывают устаревший код.

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


старый пост, но вам не нужно создавать именованный класс - использовать маски, а не как указано в этом посте PowerMock насмешливый конструктор через whennew () не работает с анонимным классом

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


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


вам нужно запустить тест с помощью powermockito runner, и вам нужно сообщить фреймворку, какой класс(ы) должен иметь пользовательское поведение. Добавьте следующие аннотации класса в тестовый класс:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ BufferedInputStream.class })