Как смоделировать частное статическое конечное поле, инициализированное с помощью частного конструктора с использованием Powermock и Mockito?

Вот мой исходный класс -

public class ClassToTest extends AbstractSuperClass<Integer> {
    private static final ClassToTest INSTANCE = new ClassToTest(); // (line 1) need to mock this variable

    static ClassToTest get() {
        return INSTANCE;
    }
    private ClassToTest() {
        super(Integer.class);// (line 2)
    }
}

Вот моя попытка проверить это

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassToTest.class)
public class TestClass {
    private ClassToTest testClass;
    @Before
    public void setUp() {
        // each of the below attempts fails at line 1  because of the call to line 2 (annotated above).
        // Attempt A.  
        testClass = WhiteBox.newInstance(ClassToTest.class);
        //Attempt B.
        testClass = mock(ClassToTest.class);
        WhiteBox.setInternalState(ClassToTest.class, "INSTANCE", testClass);
    }
    @Test
    public void dummy() {
        // irrelevant
    }
}

Я пытаюсь эффективно издеваться над ClassToTest.INSTANCE и вызывать его частный конструктор. Как я мог это сделать?

РЕДАКТИРОВАТЬ: Фрагмент/конструктор вызывается из AbstractSuperClass.

public abstract class AbstractSuperClass<V extends Serializable> {
    private final CacheClient<V> cache;
    private final int seconds;

    public AbstractSuperClass(Class<V> valueType) {
        cache = new ClientFactory(Config.getAppConfig(), StatisticSet.getGlobalStatistics()).newClient(getCacheType(), valueType);
        seconds = Config.getAppConfig().get(getCacheType().getSectionEnum()).getSeconds();
    }

P.S. Я стараюсь держаться подальше от внутренностей AbstractSuperClass и изначально надеялся просто издеваться над вызовом. Я также открыт для любых идей по рефакторингу ClassToTest, чтобы избежать этого.


person seeker    schedule 10.09.2016    source источник
comment
Ваше решение для имитации швов ClassToTest.INSTANCE работает, если вы добавите testClass.doSomething(); verify(testClass).doSomething(); к методу dummy, тогда тесты пройдут, поэтому я не вижу, в чем на самом деле проблема (я использовал попытку B). Не могли бы вы добавить дополнительную информацию о том, в чем проблема?   -  person Gergely Toth    schedule 10.09.2016
comment
@GergelyToth - я предполагаю, что это может быть связано с вызовом AbstractSuperClass из частного конструктора, я отредактировал вопрос и добавил дополнительные сведения о классе.   -  person seeker    schedule 10.09.2016
comment
Мне любопытно узнать, есть ли какие-либо решения этой проблемы с использованием Mockito/PowerMock. В моем случае я хотел протестировать другой класс, который имел ClassToTest в качестве зависимости, поэтому я в основном извлек ClassToTest в интерфейс и предоставил фиктивную реализацию для целей тестирования, аналогично тому, что объясняется здесь   -  person seeker    schedule 11.09.2016


Ответы (3)


Ваша настоящая проблема заключается в том, что вы создали сложный, почти не поддающийся тестированию код, используя static. И не только это: вы также создаете плохой дизайн. Потому что вы тесно связываете свои производственные классы друг с другом. После того, как он появится, будет трудно избавиться от этого статического метода позже.

Итак, другой вариант: вместо того, чтобы пытаться «исправить» сломанный дизайн с помощью PowerMock, вы делаете шаг назад. Вы узнаете, что такое «написание тестируемого» кода (например, посмотрев эти видео). ; а затем вы используете чистые интерфейсы и внедрение зависимостей для решения своей проблемы. И вы тестируете все это с помощью EasyMock или Mockito, без необходимости в Powermock!

person GhostCat    schedule 11.09.2016
comment
Этот. Powermock — жестокая хозяйка — она поможет вам в краткосрочной перспективе, но навредит в долгосрочной. Как уже упоминалось, он устраняет плохой дизайн, а также потребляет ваш @RunWith и может вызвать сложные проблемы совместимости с другими библиотеками из-за того, что он делает. - person markdsievers; 11.09.2016
comment
Как обычно, все в жизни, а также в IT-жизни — это балансировка. Если вы имеете дело со сторонним/устаревшим кодом; что вы можете или хотите не измениться; тогда Powermock все еще может быть полезен. Но есть очень простое правило: если вы создаете новый код, вам не нужен Powermock. Конец истории. - person GhostCat; 11.09.2016

Я не понимаю, чего вы пытаетесь достичь, но это работает здесь:

@PrepareForTest(ClassToTest.class) // required
@RunWith(PowerMockRunner.class)    // required
public class ClassToTestTest {

    private ClassToTest testClass;

    @Before
    public void setUp() throws Exception {
        this.testClass = Mockito.mock(ClassToTest.class);
        final Field instance = ClassToTest.class.getDeclaredField("INSTANCE");
        instance.setAccessible(true);
        instance.set(null, this.testClass);
    }

    @Test
    public void testGet() throws Exception {
        assertSame(this.testClass, ClassToTest.get());
        System.out.println(this.testClass);
    }
}

Вывод:

Mock for ClassToTest, hashCode: 1083021083

(Протестировано с помощью powermock-api-mockito версии 1.6.2)

person Community    schedule 11.09.2016

Я не считаю, что издевательство над полем является правильным подходом, я даже не верю, что это возможно сделать, поскольку вы не можете переопределить поле, можно переопределить только методы, что на самом деле и является способом имитации делай и работай. На самом деле макеты — это всего лишь прокси, для которых мы переопределяем методы.

Вместо этого вы должны издеваться над ClassToTest.get() следующим образом:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassToTest.class)
public class TestClass {
    private ClassToTest testClass;
    @Before
    public void setUp() {
        testClass = PowerMockito.mock(ClassToTest.class);
        PowerMockito.mockStatic(ClassToTest.class);
        Mockito.when(ClassToTest.get()).thenReturn(testClass);
    }

    @Test
    public void dummy() {
        // Here I get the instance of ClassToTest that I mocked in the setUp method
        System.out.println(ClassToTest.get());
    }
}
person Nicolas Filotto    schedule 10.09.2016
comment
Это снова не работает в PowerMockito.mock(ClassToTest.class), вызванном строкой 1 и строкой 2 соответственно, что означает, что вызов не был смоделирован :( . - person seeker; 10.09.2016
comment
Я скопировал/вставил ваш код без AbstractSuperClass, так как вы его не предоставили, и он работает - person Nicolas Filotto; 10.09.2016
comment
пожалуйста, предоставьте AbstractSuperClass, затем - person Nicolas Filotto; 10.09.2016
comment
@NicolasFilotto, я думаю, что это может быть разница в том, что мы видим разные результаты (?). В моем случае AbstractSuperClass фактически загружает кучу конфигураций, а super() может быть причиной сбоя. - person seeker; 10.09.2016
comment
трудно помочь, если вы не предоставите отсутствующий класс в соответствии с правилом MCVE stackoverflow.com/help/mcve - person Nicolas Filotto; 10.09.2016
comment
@NicolasFilotto, извините за задержку (был занят другими вещами). Я действительно добавил фрагмент из AbstractSuperClass.. Я надеюсь, что это поможет нам продвинуться вперед. - person seeker; 10.09.2016