Mockito: имитация инициализации частного поля

101

Как я могу издеваться над переменной поля, которая инициализируется встроенной?

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

Здесь я хочу поиздеваться person.someMethod()при тестировании Test.testMethod()метода, для которого мне нужно имитировать инициализацию personпеременной. Есть подсказка?

Изменить: мне не разрешено изменять класс Person.

Арун
источник
1
Эта ссылка может быть вам полезна stackoverflow.com/questions/13645571/…
Popeye
2
Вам следует провести рефакторинг своего кода, чтобы можно было передать имитацию для Person. Варианты включают добавление конструктора для этого или добавление метода установки.
Tim Biegeleisen

Ответы:

112

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, которые могут быть полезны и избавят вас от втягивания еще одной библиотеки.

Ральф
источник
Я слежу за своим целевым объектом по другим причинам, и в этом случае, когда мой объект является шпионским, я не могу установить внутреннее состояние таким образом.
Arun
Почему нет? Я использую его со шпионами. Создайте экземпляр Person. Заглушите все, что нужно, затем установите на свой тестовый экземпляр.
Ralf
Предупреждение: Whitebox находится в internalпакете и, похоже, больше не работает в Mockito 2.6.2.
Nova
Вы можете использовать FieldSetter.setField (). Ниже я привел пример того же.
Радж Кумар
1
@Ralf Потому что изменение ссылочного имени в Java всегда должно приводить к ошибке компиляции.
Joe Coder
76

Довольно поздно на вечеринку, но здесь меня ударило, и мне помог друг. Дело было не в использовании 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.
Ralf
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? Почему он внутренний? Это так полезно для тестирования.
Вилли Ментцель,
34

Если вы используете Spring Test, попробуйте 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")
Вилли Ментцель
29

Я уже нашел решение этой проблемы, которое забыл опубликовать здесь.

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

Арун
источник
9
В вашем объяснении говорится о mockStaticфункции, но она не представлена ​​в вашем примере кода. Должен ли быть mockStaticвызов в примере кода или это не требуется для конструкторов?
Shadoninja
10

Следующий код можно использовать для инициализации маппера в макете клиента 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 секунд, но в модульном тесте я доволен, если это происходит немедленно. В интеграционных тестах вы должны проверить реальное значение.

Хьюго Диас
источник