Mockito Bagaimana untuk mengejek hanya panggilan metode kelas super


94

Saya menggunakan Mockito dalam beberapa tes.

Saya memiliki kelas-kelas berikut:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

Saya hanya ingin mengejek panggilan kedua ( super.save) dari ChildService. Panggilan pertama harus memanggil metode sebenarnya. Apakah ada cara untuk melakukannya?


Bisakah ini diselesaikan dengan PowerMockito?
javaPlease42

@ javaPlease42: Ya, Anda dapat: stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

Jawaban:


57

Tidak, Mockito tidak mendukung ini.

Ini mungkin bukan jawaban yang Anda cari, tetapi yang Anda lihat adalah gejala tidak menerapkan prinsip desain:

Lebih menyukai komposisi daripada warisan

Jika Anda mengekstrak strategi alih-alih memperluas kelas super, masalahnya hilang.

Namun jika Anda tidak diizinkan untuk mengubah kode, tetapi Anda harus tetap mengujinya, dan dengan cara yang canggung ini, masih ada harapan. Dengan beberapa alat AOP (misalnya AspectJ) Anda dapat memasukkan kode ke dalam metode kelas super dan menghindari eksekusi sepenuhnya (yuck). Ini tidak berfungsi jika Anda menggunakan proxy, Anda harus menggunakan modifikasi bytecode (baik waktu muat menenun atau mengompilasi waktu tenun). Ada kerangka kerja tiruan yang mendukung jenis trik ini juga, seperti PowerMock dan PowerMockito.

Saya sarankan Anda pergi untuk refactoring, tetapi jika itu bukan pilihan Anda berada dalam kesenangan hacking yang serius.


5
Saya tidak melihat pelanggaran LSP. Saya memiliki pengaturan yang kira-kira sama dengan OP: kelas DAO dasar dengan metode findAll (), dan subkelas DAO yang menggantikan metode dasar dengan memanggil super.findAll () dan kemudian menyortir hasilnya. Subclass dapat diganti ke semua konteks yang menerima superclass tersebut. Apakah saya salah memahami maksud Anda?

1
Saya akan menghapus komentar LSP (itu tidak menambah nilai pada jawabannya).
iwein

Ya, warisan menyebalkan dan kerangka kerja bodoh yang terjebak dengan saya dirancang dengan warisan sebagai satu-satunya pilihan.
Sridhar Sarnobat

Dengan asumsi Anda tidak dapat mendesain ulang superclass, Anda dapat mengekstrak //some codeskode tersebut menjadi metode yang dapat diuji secara terpisah.
Phasmal

1
OK mengerti. Ini masalah yang berbeda dari apa yang saya coba selesaikan ketika saya mencari ini, tetapi setidaknya kesalahpahaman itu memecahkan masalah saya sendiri untuk mengejek panggilan dari kelas dasar (yang memang tidak saya timpa).
Guillaume Perrot

86

Jika Anda benar-benar tidak memiliki pilihan untuk refactoring, Anda dapat membuat tiruan / stub semuanya dalam pemanggilan metode super mis

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

2
kode ini tidak akan benar-benar mencegah pemanggilan super.save (), jadi jika Anda melakukan banyak hal di super.save () Anda harus mencegah semua panggilan itu ...
iwein

solusi fantastis bekerja sangat baik bagi saya ketika saya ingin mengembalikan nilai yang diejek dari metode superclass untuk digunakan oleh anak, terima kasih yang luar biasa.
Gurnard

14
ini berfungsi dengan baik kecuali validasi disembunyikan atau metode penyimpanan melakukan pekerjaan secara langsung daripada memanggil metode lain. mockito tidak: Mockito.doNothing (). when ((BaseService) spy) .save (); ini tidak akan 'melakukan apa pun pada layanan dasar kecuali pada penyimpanan childService :(
tibi

Saya tidak bisa mendapatkan ini untuk bekerja pada saya - itu menghentikan metode anak juga. BaseServiceabstrak, meskipun saya tidak mengerti mengapa hal itu relevan.
Sridhar Sarnobat

1
@ Sridhar-Sarnobat ya saya melihat hal yang sama :( ada yang tahu cara membuatnya hanya super.validate()
mematikan

4

Pertimbangkan untuk memfaktorkan ulang kode dari metode ChildService.save () ke metode yang berbeda dan uji metode baru tersebut daripada menguji ChildService.save (), dengan cara ini Anda akan menghindari panggilan yang tidak perlu ke metode super.

Contoh:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

1

buat metode yang dilindungi paket (anggap kelas pengujian dalam paket yang sama) di sub kelas yang memanggil metode kelas super dan kemudian panggil metode itu dalam metode sub kelas yang diganti. Anda kemudian dapat menetapkan ekspektasi pada metode ini dalam pengujian Anda melalui penggunaan pola mata-mata. tidak cantik tapi pasti lebih baik daripada harus berurusan dengan semua pengaturan harapan untuk metode super dalam pengujian Anda


dapatkah saya juga mengatakan bahwa komposisi di atas pewarisan hampir selalu lebih baik, tetapi terkadang lebih sederhana menggunakan pewarisan. sampai java memasukkan model komposisi yang lebih baik, seperti scala atau groovy, ini akan selalu terjadi dan masalah ini akan terus ada
Luke

1

Bahkan jika saya setuju dengan tanggapan iwein (

lebih menyukai komposisi daripada warisan

), saya akui ada beberapa kali pewarisan tampak wajar, dan saya tidak merasa rusak atau refactor hanya demi tes unit.

Jadi, saran saya:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

Dan kemudian, dalam pengujian unit:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done

0

Alasannya adalah kelas dasar Anda tidak bersifat publik, maka Mockito tidak dapat mencegatnya karena visibilitas, jika Anda mengubah kelas dasar sebagai publik, atau @Override di sub kelas (sebagai publik), maka Mockito dapat memalsukannya dengan benar.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

8
Ini bukan intinya. ChildService harus mengganti foo () dan masalahnya adalah bagaimana meniru BaseService.foo () tetapi tidak dengan ChildService.foo ()
Adriaan Koster

0

Mungkin opsi termudah jika warisan masuk akal adalah dengan membuat metode baru (paket pribadi ??) untuk memanggil super (sebut saja superFindall), mata-matai contoh nyata dan kemudian tiru metode superFindAll () dengan cara yang Anda inginkan untuk mengejek kelas orang tua satu. Ini bukan solusi sempurna dalam hal cakupan dan visibilitas tetapi harus melakukan pekerjaan itu dan mudah untuk diterapkan.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.