Stubbing yang Belum Selesai Terdeteksi di Mockito

151

Saya mendapatkan pengecualian berikut saat menjalankan tes. Saya menggunakan Mockito untuk mengejek. Petunjuk yang disebutkan oleh perpustakaan Mockito tidak membantu.

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
    -> at com.a.b.DomainTestFactory.myTest(DomainTestFactory.java:355)

    E.g. thenReturn() may be missing.
    Examples of correct stubbing:
        when(mock.isOk()).thenReturn(true);
        when(mock.isOk()).thenThrow(exception);
        doThrow(exception).when(mock).someVoidMethod();
    Hints:
     1. missing thenReturn()
     2. you are trying to stub a final method, you naughty developer!

        at a.b.DomainTestFactory.myTest(DomainTestFactory.java:276)
        ..........

Kode Uji dari DomainTestFactory. Ketika saya menjalankan tes berikut, saya melihat pengecualian.

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); // Line 355
}

private List<SomeModel> getSomeList() {
    SomeModel model = Mockito.mock(SomeModel.class);
    Mockito.when(model.getName()).thenReturn("SomeName"); // Line 276
    Mockito.when(model.getAddress()).thenReturn("Address");
    return Arrays.asList(model);
}

public class SomeModel extends SomeInputModel{
    protected String address;
    protected List<SomeClass> properties;

    public SomeModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    public String getAddress() {
        return this.address;
    }

}

public class SomeInputModel{

    public NetworkInputModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    protected String Name;
    protected List<SomeClass> properties;

    public String getName() {
        return this.Name;
    }

    public void setName(String value) {
        this.Name = value;
    }
}
Royal Rose
sumber
Hai Mureinik, saya telah memperbarui posting dengan nomor baris
Royal Rose

Jawaban:

371

Anda bersarang mengejek bagian dalam mengejek. Anda menelepon getSomeList(), yang melakukan beberapa ejekan, sebelum Anda selesai mengejeknya MyMainModel. Mockito tidak suka kalau kamu melakukan ini.

Menggantikan

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); --> Line 355
}

dengan

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    List<SomeModel> someModelList = getSomeList();
    Mockito.when(mainModel.getList()).thenReturn(someModelList);
}

Untuk memahami mengapa ini menyebabkan masalah, Anda perlu tahu sedikit tentang cara kerja Mockito, dan juga harus mengetahui bagaimana ekspresi dan pernyataan pesanan dievaluasi di Jawa.

Mockito tidak dapat membaca kode sumber Anda, jadi untuk mengetahui apa yang Anda minta lakukan, itu banyak bergantung pada keadaan statis. Ketika Anda memanggil metode pada objek tiruan, Mockito mencatat rincian panggilan dalam daftar doa internal. The whenmetode membaca yang terakhir dari doa tersebut dari daftar dan mencatat doa ini dalam OngoingStubbingobjek itu kembali.

Garis

Mockito.when(mainModel.getList()).thenReturn(someModelList);

menyebabkan interaksi berikut dengan Mockito:

  • Metode mock mainModel.getList()disebut,
  • Metode statis whendisebut,
  • Metode thenReturndipanggil pada OngoingStubbingobjek yang dikembalikan oleh whenmetode.

The thenReturnMetode kemudian dapat menginstruksikan mock itu diterima melalui OngoingStubbingmetode untuk menangani setiap panggilan sesuai dengan getListmetode untuk kembali someModelList.

Bahkan, karena Mockito tidak dapat melihat kode Anda, Anda juga dapat menulis mengejek Anda sebagai berikut:

mainModel.getList();
Mockito.when((List<SomeModel>)null).thenReturn(someModelList);

Gaya ini agak kurang jelas untuk dibaca, terutama karena dalam kasus nullini harus dicetak, tetapi menghasilkan urutan interaksi yang sama dengan Mockito dan akan mencapai hasil yang sama seperti baris di atas.

Namun, garisnya

Mockito.when(mainModel.getList()).thenReturn(getSomeList());

menyebabkan interaksi berikut dengan Mockito:

  1. Metode mock mainModel.getList()disebut,
  2. Metode statis whendisebut,
  3. Sebuah baru mockdari SomeModeldiciptakan (di dalam getSomeList()),
  4. Metode mock model.getName()disebut,

Pada titik ini Mockito menjadi bingung. Sepertinya Anda mengejek mainModel.getList(), tetapi sekarang Anda mengatakan bahwa Anda ingin mengejek model.getName()metodenya. Untuk Mockito, sepertinya Anda melakukan hal berikut:

when(mainModel.getList());
// ...
when(model.getName()).thenReturn(...);

Ini terlihat konyol Mockitokarena tidak yakin dengan apa yang Anda lakukan mainModel.getList().

Perhatikan bahwa kami tidak sampai ke thenReturnpemanggilan metode, karena JVM perlu mengevaluasi parameter untuk metode ini sebelum dapat memanggil metode. Dalam hal ini, ini berarti memanggil getSomeList()metode.

Secara umum itu adalah keputusan desain yang buruk untuk bergantung pada keadaan statis, seperti yang dilakukan Mockito, karena itu dapat menyebabkan kasus-kasus di mana Prinsip Least Astonishment dilanggar. Namun, desain Mockito memang membuat ejekan yang jelas dan ekspresif, meskipun kadang-kadang membuat orang tercengang.

Akhirnya, versi terbaru dari Mockito menambahkan baris tambahan ke pesan kesalahan di atas. Baris tambahan ini menunjukkan Anda mungkin berada dalam situasi yang sama dengan pertanyaan ini:

3: Anda mematikan perilaku mock lain di dalam sebelum instruksi 'thenReturn' jika selesai

Luke Woodward
sumber
Apakah ada penjelasan tentang fakta ini? Solusi bekerja. Dan saya tidak mengerti mengapa penciptaan tiruan 'di tempat' tidak berhasil. Ketika Anda membuat tiruan dan melewati tiruan yang dibuat dengan merujuk ke tiruan lain, itu berhasil.
Capacytron
1
Jawaban sempurna, cinta SO! Saya perlu waktu lama untuk menemukannya sendiri
Dici
4
Jawaban yang bagus Luke! Penjelasan yang sangat rinci dalam kata-kata sederhana. Terima kasih.
Tomasz Kalkosiński
1
Luar biasa. Lucunya, ketika saya melakukan panggilan metode langsung dan debug perlahan, maka itu berhasil. Atribut CGLIB $ BOUND akan mendapatkan nilai yang benar, tetapi bagaimanapun membutuhkan sedikit waktu. Ketika saya menggunakan metode panggilan langsung dan berhenti sebelum pelatihan (ketika ...), maka saya melihat nilainya pertama salah, dan kemudian menjadi benar. Ketika itu salah dan pelatihan dimulai, maka pengecualian ini terjadi.
Michael Hegner
Anda membuat hari saya! Ini adalah jenis kesalahan yang membuat Anda membuang banyak waktu! Saya pikir itu adalah sesuatu yang berkaitan dengan kotlin pada awalnya
Bronx
1
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
E.g. thenReturn() may be missing.

Untuk mengejek metode batal, coba di bawah ini:

//Kotlin Syntax

 Mockito.`when`(voidMethodCall())
           .then {
                Unit //Do Nothing
            }
takharsh
sumber