Cara mengejek metode batal dengan Mockito

938

Bagaimana cara mengejek metode dengan tipe batal?

Saya menerapkan pola pengamat tetapi saya tidak bisa mengejeknya dengan Mockito karena saya tidak tahu caranya.

Dan saya mencoba mencari contoh di Internet tetapi tidak berhasil.

Kelas saya terlihat seperti ini:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

Sistem tidak dipicu dengan mock.

Saya ingin menunjukkan status sistem yang disebutkan di atas. Dan buatlah pernyataan menurut mereka.

ibrahimyilmaz
sumber
6
Berhati-hatilah karena metode batal pada tiruan tidak melakukan apa pun secara default!
Baris
1
@ Line, itulah yang saya cari. Tampak jelas setelah Anda mengatakannya. Tapi itu menyoroti prinsip mengejek: Anda hanya perlu mengejek metode kelas mengejek untuk efeknya, seperti nilai balik atau pengecualian. Terima kasih!
allenjom

Jawaban:

1145

Lihatlah dokumen Mockito API . Sebagai dokumen terkait menyebutkan (Point No. 12) Anda dapat menggunakan salah satu doThrow(), doAnswer(), doNothing(), doReturn()keluarga metode dari kerangka Mockito untuk mengejek metode batal.

Sebagai contoh,

Mockito.doThrow(new Exception()).when(instance).methodName();

atau jika Anda ingin menggabungkannya dengan perilaku tindak lanjut,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

Menganggap bahwa Anda mencari mengejek setter setState(String s)di kelas Dunia di bawah ini adalah kode menggunakan doAnswermetode untuk mengejek setState.

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());
sateesh
sumber
8
@ equalidafial: Ya, saya pikir parameterisasi untuk Void akan lebih baik karena lebih baik menyampaikan bahwa saya tidak tertarik pada tipe pengembalian. Saya tidak mengetahui konstruk ini, terima kasih telah menunjukkannya.
sateesh
2
doThrow adalah # 5 sekarang (juga untuk saya yang menggunakan doThrow memperbaiki pesan "'void' type not
allow
@ equalidafial: Saya pikir jenis kembalinya panggilan Answer.answer bukanlah apa yang akan dikembalikan ke metode asli, melainkan apa yang dikembalikan ke panggilan doAnswer, mungkin jika Anda ingin melakukan sesuatu yang lain dengan nilai itu dalam pengujian Anda.
belas17
1
:( dalam mencoba Mock versi 16.0.1 dari RateLimiter.java di jambu doNothing (). when (mockLimiterReject) .setRate (100) menghasilkan panggilan setRate dari RateLimiter yang menghasilkan nullpointer karena mutex adalah null karena suatu alasan begitu mockito bytecoded begitu itu tidak mengejek metode setRate saya :( tetapi sebaliknya menyebutnya :(
Dean Hiller
2
Pemberitahuan @DeanHiller yang setRate()adalah final, dan karena itu tidak dapat dipermainkan. Alih-alih mencoba create()contoh yang melakukan apa yang Anda butuhkan. Seharusnya tidak perlu mengejek RateLimiter.
dimo414
113

Saya pikir saya telah menemukan jawaban yang lebih sederhana untuk pertanyaan itu, untuk memanggil metode nyata hanya untuk satu metode (bahkan jika itu memiliki pengembalian kosong) Anda dapat melakukan ini:

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

Atau, Anda bisa memanggil metode nyata untuk semua metode kelas itu, melakukan ini:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);
MarcioB
sumber
13
Inilah jawaban sebenarnya di sini. Metode spy () berfungsi dengan baik, tetapi umumnya dicadangkan ketika Anda ingin objek melakukan hampir semua hal secara normal.
biggusjimmus
1
Apa artinya ini? Apakah Anda benar-benar memanggil metode? Saya belum pernah menggunakan mockito sebelumnya.
obesechicken13
Ya, mock akan memanggil metode yang sebenarnya. Jika Anda menggunakan @Mock Anda dapat menentukan yang sama dengan: @Mock (answer = Answers.CALLS_REAL_METHODS) untuk mendapatkan hasil yang sama.
Ale
70

Menambahkan apa yang dikatakan @sateesh, ketika Anda hanya ingin mengejek metode kosong untuk mencegah tes memanggilnya, Anda bisa menggunakan Spycara ini:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

Ketika Anda ingin menjalankan tes Anda, pastikan Anda memanggil metode dalam pengujian pada spyobjek dan bukan pada worldobjek. Sebagai contoh:

assertEquals(0,spy.methodToTestThatShouldReturnZero());
Omri374
sumber
57

Solusi masalah yang disebut adalah dengan menggunakan spy Mockito.spy (...) alih-alih mock Mockito.mock (..) .

Spy memungkinkan kita untuk mengejek sebagian. Mockito pandai dalam hal ini. Karena Anda memiliki kelas yang tidak lengkap, dengan cara ini Anda mengejek beberapa tempat yang diperlukan di kelas ini.

ibrahimyilmaz
sumber
3
Saya tersandung di sini karena saya memiliki masalah yang sama (juga, kebetulan, sedang menguji interaksi Subjek / Pengamat). Saya sudah menggunakan mata-mata tetapi saya ingin metode 'SubjectChanged' melakukan sesuatu yang berbeda. Saya bisa menggunakan `memverifikasi (pengamat) .subjectChanged (subjek) hanya untuk melihat bahwa metode itu dipanggil. Tapi, untuk beberapa alasan, saya lebih suka mengganti metode. Untuk itu, kombinasi dari pendekatan Sateesh dan jawaban Anda di sini adalah cara untuk pergi ...
gMale
32
Tidak, melakukan ini tidak akan benar-benar membantu dengan metode batal mengejek Caranya adalah dengan menggunakan salah satu dari empat metode statis Mockito yang tercantum dalam jawaban sateesh.
Dawood ibn Kareem
2
@Gurnard untuk pertanyaan Anda, lihat stackoverflow.com/questions/1087339/… ini .
ibrahimyilmaz
Dosis ini benar-benar berfungsi.
Nilotpal
34

Pertama-tama: Anda harus selalu mengimpor mockito statis, dengan cara ini kode akan jauh lebih mudah dibaca (dan intuitif):

import static org.mockito.Mockito.*;

Untuk mengejek parsial dan tetap mempertahankan fungsionalitas asli, mockito lainnya menawarkan "Spy".

Anda dapat menggunakannya sebagai berikut:

private World world = spy(World.class);

Untuk menghilangkan metode dari eksekusi Anda dapat menggunakan sesuatu seperti ini:

doNothing().when(someObject).someMethod(anyObject());

untuk memberikan beberapa perilaku khusus ke metode, gunakan "when" dengan "thenReturn":

doReturn("something").when(this.world).someMethod(anyObject());

Untuk contoh lebih lanjut, silakan temukan sampel mockito yang sangat baik dalam dokumen.

fl0w
sumber
4
Apa hubungannya impor statis dengan membuatnya lebih mudah dibaca?
jsonbourne
2
secara harfiah tidak ada.
spesializ
2
Saya pikir ini masalah selera, saya hanya ingin memiliki pernyataan terlihat (hampir) seperti kalimat bahasa Inggris, dan Class.methodname (). Something () vs methodname () .sesuatu kurang mudah dibaca.
fl0w
1
Jika Anda bekerja di sebuah tim, impor statis membuatnya susah payah bagi orang lain untuk melihat dari mana metode ini berasal, karena sering ada wildcard dalam impor.
LowKeyEnergy
itu benar, namun untuk kode Uji dan Mockito ini harus jelas dan bukan masalah.
fl0w
27

Cara mempermainkan metode batal dengan mockito - ada dua opsi:

  1. doAnswer - Jika kita ingin metode batal mengejek kita untuk melakukan sesuatu (mengejek perilaku meskipun batal).
  2. doThrow- Lalu ada Mockito.doThrow()jika Anda ingin melempar pengecualian dari metode batal diejek.

Berikut ini adalah contoh cara menggunakannya (bukan usecase yang ideal tetapi hanya ingin menggambarkan penggunaan dasar).

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("[email protected]")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

}
}

Anda dapat menemukan rincian lebih lanjut tentang cara mengejek dan menguji metode batal dengan Mockito di posting saya Cara mengejek dengan Mockito (Panduan komprehensif dengan contoh)

Dilini Rajapaksha
sumber
1
Teladan besar Catatan: Di java 8, mungkin sedikit lebih baik menggunakan lambda daripada kelas anonim: doa doAnswer ((Jawab <Void>) -> {// CODE}). When (mockInstance) .add (metode ()); '
miwe
16

Menambahkan jawaban lain ke banyak (tidak ada permainan kata pun dimaksudkan) ...

Anda perlu memanggil metode doAnswer jika Anda tidak dapat \ tidak ingin menggunakan mata-mata. Namun, Anda tidak perlu menggulung Jawaban Anda sendiri . Ada beberapa implementasi standar. Khususnya, CallsRealMethods .

Dalam praktiknya, tampilannya seperti ini:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

Atau:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));
javamonkey79
sumber
16

Di Java 8 ini dapat dibuat sedikit lebih bersih, dengan asumsi Anda memiliki impor statis untuk org.mockito.Mockito.doAnswer:

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

Itu return null;penting dan tanpa itu kompilasi akan gagal dengan beberapa kesalahan yang cukup tidak jelas karena tidak akan dapat menemukan penggantian yang sesuai doAnswer.

Misalnya sebuah ExecutorServiceyang langsung mengeksekusi yang Runnablediteruskan ke execute()dapat diimplementasikan menggunakan:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());
Tim B
sumber
2
Dalam satu baris: Mockito.doAnswer ((i) -> null) .when (instance) .method (any ());
Akshay Thorve
@ AkshayThorve Itu tidak berfungsi ketika Anda benar-benar ingin melakukan hal-hal dengan saya.
Tim B
7

Saya pikir masalah Anda disebabkan oleh struktur pengujian Anda. Saya merasa sulit untuk mencampur ejekan dengan metode tradisional mengimplementasikan antarmuka di kelas uji (seperti yang Anda lakukan di sini).

Jika Anda menerapkan pendengar sebagai Mock, Anda dapat memverifikasi interaksinya.

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

Ini seharusnya memuaskan Anda bahwa 'Dunia' melakukan hal yang benar.

ashley
sumber
0

Menggunakan Mockito.doThrow seperti pada:

Mockito.doThrow(new Exception()).when(instance).methodName();

Anda dapat mencoba contoh yang bagus ini:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

Sumber: http://apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

APISonar
sumber