Mockito: Імітація ініціалізації приватного поля


95

Як я можу знущатися над змінною поля, яка ініціалізується вбудовано?

class Test {
    private Person person = new Person();
    ...
    public void testMethod() {
        person.someMethod();
        ...
    }
}

Тут я хочу знущатися person.someMethod()під час тестування Test.testMethod()методу, для якого мені потрібно знущатися над ініціалізацією personзмінної. Будь-яка підказка?

Редагувати: Мені не дозволено змінювати клас Person.


1
Це посилання може бути вам корисним stackoverflow.com/questions/13645571/…
Попай

2
Вам слід перефактурувати свій код, щоб ви могли передати макет для Person. Варіанти включають додавання конструктора для цього або додавання методу встановлення.
Тім Біглейзен

Відповіді:


109

Mockito постачається з допоміжним класом, щоб заощадити вам якийсь код відображення котлових пластин:

import org.mockito.internal.util.reflection.Whitebox;

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    Whitebox.setInternalState(underTest, "person", mockedPerson);
    // ...
}

Оновлення: На жаль, команда mockito вирішила видалити клас у Mockito 2. Отже, ви повернулися до написання власного зразкового коду відображення, використовуєте іншу бібліотеку (наприклад, Apache Commons Lang ) або просто розкрадаєте клас Whitebox (він має ліцензію MIT ).

Оновлення 2: JUnit 5 постачається зі своїми власними класами ReflectionSupport та AnnotationSupport, які можуть бути корисними та позбавлять вас від втягування ще однієї бібліотеки.


Я шпигую за своїм цільовим об'єктом з інших причин, і в цьому випадку, коли мій об'єкт шпигує, я не можу встановити внутрішній стан таким чином.
Арун

Чому ні? Я використовую його зі шпигунами. Створіть екземпляр Person. Заглушіть все, що потребує заглушення, а потім встановіть це на своєму тестовому екземплярі.
Ральф,

Попередження: Whitebox є в internalпакеті і, схоже, більше не працює на Mockito 2.6.2.
Нова

Ви можете використовувати FieldSetter.setField (). Я навів приклад нижче для того ж.
Радж Кумар

1
@Ralf Оскільки зміна посилального імені в Java завжди повинно призводити до помилки компіляції.
Джо Кодер,

69

Досить пізно на вечірку, але мене тут вразили і я отримав допомогу від друга. Річ у тому, щоб не використовувати PowerMock. Це працює з останньою версією Mockito.

Mockito йде з цим org.mockito.internal.util.reflection.FieldSetter.

Що в основному робить, це допомагає вам змінювати приватні поля за допомогою відображення.

Ось як ви ним користуєтесь:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);
    // ...
    verify(mockedPerson).someMethod();
}

Таким чином ви можете передати макетний об'єкт, а потім перевірити його пізніше.

Ось посилання.


FieldSetterбільше не доступний у Mockito 2.x.
Ральф,

4
@Ralf Я використовую версію mockito-core-2.15.0. Він доступний там. static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/… . Все-таки бета-версія.
Радж Кумар,

1
@RajKumar Class#getDeclaredFieldприймає єдиний параметр, тому панелі повинні виглядати так FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);. Саме так я помилився і вважав, що це може бути гарною ідеєю виправити це у вашому прикладі. Дякуємо за відповідь.
Dapeng Li

1
використання внутрішнього API - не найкраща ідея
Девід

@RajKumar Хороший, але як згадував Зімбо Роджер: чи є гарною ідеєю використовувати внутрішній API? Чому це внутрішньо гостро? Це так корисно для тестування.
Віллі Менцель

32

Якщо ви використовуєте весняний тест, спробуйте org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);

1
Чудово! Може допомогти зробити посилання на цю статтю і, можливо, включити залежності, які від неї потрібні: baeldung.com/spring-reflection-test-utils
Віллі Менцель

build.gradle.kts:testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
Віллі Менцель

28

Я вже знайшов рішення цієї проблеми, яке забув розмістити тут.

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Test.class })
public class SampleTest {

@Mock
Person person;

@Test
public void testPrintName() throws Exception {
    PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);
    Test test= new Test();
    test.testMethod();
    }
}

Ключовими моментами цього рішення є:

  1. Запуск моїх тестових випадків за допомогою PowerMockRunner: @RunWith(PowerMockRunner.class)

  2. Доручити Powermock підготуватися Test.classдо маніпулювання приватними полями:@PrepareForTest({ Test.class })

  3. І нарешті знущаємось над конструктором для класу Person:

    PowerMockito.mockStatic(Person.class); PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);


8
У вашому поясненні йдеться про mockStaticфункцію, але вона не представлена ​​у вашому прикладі коду. Чи повинен приклад коду мати mockStaticвиклик, чи це не потрібно для конструкторів?
Shadoninja

10

Наступний код може бути використаний для ініціалізації mapper у макеті клієнта REST. mapperПоле є приватним і має бути встановлено під час установки модульного тестування.

import org.mockito.internal.util.reflection.FieldSetter;

new FieldSetter(client, Client.class.getDeclaredField("mapper")).set(new Mapper());

5

За допомогою посібника @ Jarda ви можете визначити це, якщо вам потрібно встановити для змінної однакове значення для всіх тестів:

@Before
public void setClientMapper() throws NoSuchFieldException, SecurityException{
    FieldSetter.setField(client, client.getClass().getDeclaredField("mapper"), new Mapper());
}

Але майте на увазі, що налаштування приватних цінностей на різні слід поводитись обережно. Якщо вони приватні - це з якихось причин.

Наприклад, я використовую його, наприклад, для зміни часу очікування сну в модульних тестах. У реальних прикладах я хочу спати 10 секунд, але в модульному тесті я задоволений, якщо це негайно. В інтеграційних тестах ви повинні перевірити реальне значення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.