Mockito: Mock inisialisasi bidang pribadi

101

Bagaimana saya dapat membuat tiruan variabel bidang yang sedang diinisialisasi?

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

Di sini saya ingin mengejek person.someMethod()saat menguji Test.testMethod()metode yang saya perlukan untuk meniru inisialisasi personvariabel. Ada petunjuk?

Edit: Saya tidak diizinkan untuk mengubah kelas Person.

Arun
sumber
1
Tautan ini mungkin berguna bagi Anda stackoverflow.com/questions/13645571/…
Popeye
2
Anda harus memfaktorkan ulang kode Anda sehingga Anda dapat mengirimkan tiruan untuk Person. Opsi termasuk menambahkan konstruktor untuk melakukan ini, atau menambahkan metode penyetel.
Tim Biegeleisen

Jawaban:

112

Mockito hadir dengan kelas pembantu untuk menghemat beberapa kode pelat boiler refleksi:

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

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

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

Pembaruan: Sayangnya tim mockito memutuskan untuk menghapus kelas di Mockito 2. Jadi Anda kembali menulis kode boilerplate refleksi Anda sendiri, gunakan pustaka lain (misalnya Apache Commons Lang ), atau cukup mencuri kelas Whitebox (itu berlisensi MIT ).

Pembaruan 2: JUnit 5 hadir dengan kelas ReflectionSupport dan AnnotationSupport -nya sendiri yang mungkin berguna dan menyelamatkan Anda dari menarik perpustakaan lain.

Ralf
sumber
Saya memata-matai objek target saya karena alasan lain dan dalam kasus ini ketika objek saya adalah mata-mata, saya tidak dapat menyetel keadaan internal dengan cara ini.
Arun
Kenapa tidak? Saya menggunakannya dengan mata-mata. Buat instance Person. Stub apa pun yang perlu dihentikan, lalu setel pada instance Test Anda.
Ralf
Peringatan: Whitebox ada di dalam internalpaketnya dan sepertinya tidak berfungsi lagi di Mockito 2.6.2.
Nova
Anda bisa menggunakan FieldSetter.setField (). Saya telah memberikan contoh di bawah ini untuk hal yang sama.
Raj Kumar
1
@Ralf Karena mengubah nama referensi di Java harus selalu menghasilkan kesalahan kompilasi.
Joe Coder
76

Cukup terlambat ke pesta, tapi saya terpukul di sini dan mendapat bantuan dari seorang teman. Masalahnya adalah tidak menggunakan PowerMock. Ini berfungsi dengan versi terbaru Mockito.

Mockito datang dengan ini org.mockito.internal.util.reflection.FieldSetter.

Apa yang pada dasarnya dilakukannya adalah membantu Anda mengubah bidang pribadi menggunakan refleksi.

Inilah cara Anda menggunakannya:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

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

Dengan cara ini Anda bisa melewatkan objek tiruan dan kemudian memverifikasinya nanti.

Berikut referensinya.

Raj Kumar
sumber
FieldSettertidak tersedia lagi di Mockito 2.x.
Ralf
4
@Ralf Saya menggunakan versi mockito-core-2.15.0. Itu tersedia di sana. static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/… . Masih beta.
Raj Kumar
1
@RajKumar Class#getDeclaredFieldmenerima parameter tunggal, jadi orang tua harus terlihat seperti ini FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);. Begitulah cara saya salah dan berpikir mungkin ide yang bagus untuk memperbaikinya dalam contoh Anda. Terima kasih sudah membalas.
Dapeng Li
1
menggunakan API internal bukanlah ide terbaik
David
@RajKumar Bagus, tetapi seperti yang disebutkan Zimbo Rodger: Apakah sebaiknya menggunakan API internal? Mengapa internal secara akut? Ini sangat berguna untuk pengujian.
Willi Mentzel
34

Jika Anda menggunakan Uji Musim Semi, coba org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);
David
sumber
1
Bagus! Mungkin membantu untuk menautkan ke artikel ini dan mungkin menyertakan dependensi yang dibutuhkan darinya: baeldung.com/spring-reflection-test-utils
Willi Mentzel
build.gradle.kts:testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
Willi Mentzel
29

Saya sudah menemukan solusi untuk masalah ini yang saya lupa posting di sini.

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

Poin utama dari solusi ini adalah:

  1. Menjalankan kasus pengujian saya dengan PowerMockRunner: @RunWith(PowerMockRunner.class)

  2. Perintahkan Powermock untuk mempersiapkan Test.classmanipulasi bidang pribadi:@PrepareForTest({ Test.class })

  3. Dan akhirnya mengejek konstruktor untuk kelas Person:

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

Arun
sumber
9
Penjelasan Anda berbicara tentang mockStaticfungsi tersebut, tetapi itu tidak terwakili dalam contoh kode Anda. Haruskah contoh kode memiliki mockStaticpanggilan, atau apakah itu tidak diperlukan untuk konstruktor?
Shadoninja
10

Kode berikut dapat digunakan untuk menginisialisasi mapper di mock klien REST. The mapperbidang pribadi dan harus ditetapkan selama setup unit tes.

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

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

Menggunakan panduan @ Jarda, Anda dapat menentukan ini jika Anda perlu menyetel variabel dengan nilai yang sama untuk semua pengujian:

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

Namun berhati-hatilah bahwa menetapkan nilai pribadi menjadi berbeda harus ditangani dengan hati-hati. Jika mereka pribadi karena alasan tertentu.

Contoh, saya menggunakannya, misalnya, untuk mengubah waktu tunggu tidur di unit test. Dalam contoh nyata saya ingin tidur selama 10 detik tetapi dalam unit-test saya puas jika langsung. Dalam pengujian integrasi, Anda harus menguji nilai sebenarnya.

Hugo Dias
sumber