Mockito: Mock private Feldinitialisierung

94

Wie kann ich eine Feldvariable verspotten, die inline initialisiert wird?

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

Hier möchte ich verspotten, person.someMethod()während ich die Test.testMethod()Methode teste , für die ich die Initialisierung einer personVariablen verspotten muss . Irgendeine Ahnung?

Bearbeiten: Ich darf die Personenklasse nicht ändern.

Arun
quelle
1
Dieser Link könnte für Sie hilfreich sein stackoverflow.com/questions/13645571/…
Popeye
2
Sie sollten Ihren Code umgestalten, damit Sie einen Mock für übergeben können Person. Zu den Optionen gehören das Hinzufügen eines Konstruktors oder eine Setter-Methode.
Tim Biegeleisen

Antworten:

108

Mockito wird mit einer Helferklasse geliefert, um Ihnen den Code der Reflexionskesselplatte zu ersparen:

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

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

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

Update: Leider hat das Mockito-Team beschlossen , die Klasse in Mockito 2 zu entfernen. Sie können also wieder Ihren eigenen Reflection-Boilerplate-Code schreiben, eine andere Bibliothek (z. B. Apache Commons Lang ) verwenden oder einfach die Whitebox- Klasse stehlen (sie ist MIT-lizenziert ).

Update 2: JUnit 5 enthält eigene ReflectionSupport- und AnnotationSupport- Klassen, die möglicherweise nützlich sind und Sie vor dem Abrufen einer weiteren Bibliothek bewahren.

Ralf
quelle
Ich spioniere mein Zielobjekt aus anderen Gründen aus. In diesem Fall kann ich den internen Status nicht festlegen, wenn mein Objekt ausspioniert ist.
Arun
Warum nicht? Ich benutze es mit Spionen. Erstellen Sie eine Personeninstanz. Stub, was auch immer gestubbt werden muss, und dann auf Ihrer Testinstanz festlegen.
Ralf
Warnung: Whitebox ist im internalPaket enthalten und scheint unter Mockito 2.6.2 nicht mehr zu funktionieren.
Nova
Sie können FieldSetter.setField () verwenden. Ich habe unten ein Beispiel dafür gegeben.
Raj Kumar
1
@Ralf Da das Ändern eines Referenznamens in Java immer zu einem Kompilierungsfehler führen sollte.
Joe Coder
67

Ziemlich spät zur Party, aber ich wurde hier geschlagen und bekam Hilfe von einem Freund. Die Sache war, PowerMock nicht zu benutzen. Dies funktioniert mit der neuesten Version von Mockito.

Mockito kommt damit org.mockito.internal.util.reflection.FieldSetter.

Grundsätzlich hilft es Ihnen, private Felder mithilfe von Reflektion zu ändern.

So verwenden Sie es:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

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

Auf diese Weise können Sie ein Scheinobjekt übergeben und später überprüfen.

Hier ist die Referenz.

Raj Kumar
quelle
FieldSetterist in Mockito 2.x nicht mehr verfügbar.
Ralf
4
@Ralf Ich verwende die Version mockito-core-2.15.0. Es ist dort verfügbar. static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/… . Trotzdem Beta.
Raj Kumar
1
@ RajKumar der Class#getDeclaredFieldakzeptiert einzelne Parameter, also müssen die Eltern so aussehen FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);. So habe ich es falsch verstanden und dachte, es wäre eine gute Idee, es in Ihrem Beispiel zu beheben. Danke für Ihre Antwort.
Dapeng Li
1
Die Verwendung einer internen API ist nicht die beste Idee
David
@ RajKumar Schön, aber wie Zimbo Rodger sagte: Ist es eine gute Idee, eine interne API zu verwenden? Warum ist es akut intern? Es ist so nützlich zum Testen.
Willi Mentzel
31

Wenn Sie Spring Test verwenden, versuchen Sie es mit org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);
David
quelle
1
Toll! Könnte helfen, auf diesen Artikel zu verlinken und möglicherweise die Abhängigkeiten aufzunehmen, die man davon benötigt: baeldung.com/spring-reflection-test-utils
Willi Mentzel
build.gradle.kts:testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
Willi Mentzel
28

Ich habe bereits die Lösung für dieses Problem gefunden, die ich hier vergessen habe zu posten.

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

Wichtige Punkte für diese Lösung sind:

  1. Ausführen meiner Testfälle mit PowerMockRunner: @RunWith(PowerMockRunner.class)

  2. Weisen Sie Powermock an, sich Test.classauf die Manipulation privater Felder vorzubereiten :@PrepareForTest({ Test.class })

  3. Und schließlich verspotten Sie den Konstruktor für die Personenklasse:

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

Arun
quelle
7
Ihre Erklärung spricht über die mockStaticFunktion, aber das ist in Ihrem Codebeispiel nicht dargestellt. Sollte das Codebeispiel den mockStaticAufruf haben oder ist dies für Konstruktoren nicht erforderlich?
Shadoninja
10

Der folgende Code kann verwendet werden, um den Mapper im REST-Client-Mock zu initialisieren. Das mapperFeld ist privat und muss während der Einrichtung des Komponententests festgelegt werden.

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

new FieldSetter(client, Client.class.getDeclaredField("mapper")).set(new Mapper());
Jarda Pavlíček
quelle
5

Mit der Anleitung von @ Jarda können Sie dies definieren, wenn Sie für alle Tests den gleichen Wert für die Variable festlegen müssen:

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

Beachten Sie jedoch, dass die Einstellung privater Werte unterschiedlich sein sollte. Wenn sie privat sind, sind aus irgendeinem Grund.

Beispiel, ich benutze es zum Beispiel, um die Wartezeit eines Schlafes in den Unit-Tests zu ändern. In realen Beispielen möchte ich 10 Sekunden schlafen, aber im Unit-Test bin ich zufrieden, wenn es sofort ist. In Integrationstests sollten Sie den tatsächlichen Wert testen.

Hugo Dias
quelle