Mockito: Mock inicjalizacja pola prywatnego

100

Jak mogę mockować zmienną pola, która jest inicjowana w tekście?

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

Tutaj chcę mockować person.someMethod()podczas testowania Test.testMethod()metody, dla której muszę mockować inicjalizację personzmiennej. Jakaś wskazówka?

Edycja: nie mogę modyfikować klasy osoby.

Bieg
źródło
1
Ten link może być pomocny. Stackoverflow.com/questions/13645571/ ...
Popeye
2
Powinieneś refaktoryzować swój kod, aby można było przekazać próbę Person. Opcje obejmują dodanie konstruktora, aby to zrobić, lub dodanie metody ustawiającej.
Tim Biegeleisen

Odpowiedzi:

112

Mockito jest dostarczany z klasą pomocniczą, która pozwala zaoszczędzić trochę kodu płytki kotła:

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

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

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

Aktualizacja: Niestety, zespół mockito zdecydował się usunąć klasę z Mockito 2. Wracasz więc do pisania własnego standardowego kodu refleksji, użyj innej biblioteki (np. Apache Commons Lang ) lub po prostu okradnij klasę Whitebox (jest na licencji MIT ).

Aktualizacja 2: JUnit 5 zawiera własne klasy ReflectionSupport i AnnotationSupport , które mogą być przydatne i uchronić Cię przed ściąganiem kolejnej biblioteki.

Ralf
źródło
Szpieguję obiekt docelowy z innych powodów iw tym przypadku, gdy mój obiekt jest szpiegiem, nie mogę w ten sposób ustawić stanu wewnętrznego.
Arun
Dlaczego nie? Używam go ze szpiegami. Utwórz instancję Person. Usuń wszystko, co wymaga stubbingu, a następnie ustaw je w instancji Test.
Ralf
Ostrzeżenie: Whitebox jest w internalpakiecie i nie wydaje się już działać na Mockito 2.6.2.
Nova
Możesz użyć FieldSetter.setField (). Poniżej podałem przykład tego samego.
Raj Kumar
1
@Ralf Ponieważ zmiana nazwy odwołania w Javie powinna zawsze skutkować błędem kompilacji.
Joe Coder
73

Dość późno na przyjęcie, ale uderzyło mnie tutaj i otrzymałem pomoc od znajomego. Nie chodziło o to, żeby użyć PowerMocka. Działa to z najnowszą wersją Mockito.

Mockito przychodzi z tym org.mockito.internal.util.reflection.FieldSetter .

Zasadniczo pomaga modyfikować prywatne pola za pomocą odbicia.

Oto jak tego używasz:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

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

W ten sposób możesz przekazać pozorowany obiekt, a następnie zweryfikować go później.

Oto odniesienie.

Raj Kumar
źródło
FieldSetternie jest już dostępne w Mockito 2.x.
Ralf
4
@Ralf Używam wersji mockito-core-2.15.0. Jest tam dostępny. static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/… . Jednak nadal beta.
Raj Kumar
1
@RajKumar Class#getDeclaredFieldakceptuje pojedynczy parametr, więc pareny muszą wyglądać tak FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);. W ten sposób pomyliłem się i pomyślałem, że dobrym pomysłem może być naprawienie tego na twoim przykładzie. Dzięki za odpowiedź.
Dapeng Li
1
używanie wewnętrznego API nie jest najlepszym pomysłem
David
@RajKumar Fajnie, ale jak wspomniał Zimbo Rodger: Czy warto używać wewnętrznego API? Dlaczego jest ostry wewnętrzny? Jest to bardzo przydatne do testowania.
Willi Mentzel
34

Jeśli używasz Spring Test, wypróbuj org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);
David
źródło
1
Świetny! Może pomóc link do tego artykułu i może zawierać zależności, których potrzebujesz: baeldung.com/spring-reflection-test-utils
Willi Mentzel
build.gradle.kts:testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
Willi Mentzel
29

Znalazłem już rozwiązanie tego problemu, o którym zapomniałem zamieścić tutaj.

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

Kluczowe punkty tego rozwiązania to:

  1. Uruchamiam moje przypadki testowe z PowerMockRunner: @RunWith(PowerMockRunner.class)

  2. Poinstruuj Powermocka, aby przygotował się Test.classdo manipulacji polami prywatnymi:@PrepareForTest({ Test.class })

  3. I na koniec mock konstruktora dla klasy Person:

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

Bieg
źródło
9
Twoje wyjaśnienie mówi o mockStaticfunkcji, ale nie jest to przedstawione w Twoim przykładzie kodu. Czy przykładowy kod powinien mieć mockStaticwywołanie, czy nie jest to wymagane dla konstruktorów?
Shadoninja
10

Poniższy kod może służyć do inicjalizacji programu odwzorowującego w makiecie klienta REST. To mapperpole jest prywatne i należy je ustawić podczas konfiguracji testu jednostkowego.

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

new FieldSetter(client, Client.class.getDeclaredField("mapper")).set(new Mapper());
Jarda Pavlíček
źródło
5

Korzystając z przewodnika @ Jarda, możesz to zdefiniować, jeśli chcesz ustawić zmienną tę samą wartość dla wszystkich testów:

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

Uważaj jednak, aby zachować ostrożność przy ustawianiu innych wartości prywatnych. Jeśli są prywatne, z jakiegoś powodu.

Na przykład używam go, na przykład, do zmiany czasu oczekiwania uśpienia w testach jednostkowych. W prawdziwych przykładach chcę spać przez 10 sekund, ale w teście jednostkowym jestem zadowolony, jeśli jest to natychmiastowe. W testach integracyjnych powinieneś przetestować rzeczywistą wartość.

Hugo Dias
źródło