Mockito - @Spy vs @Mock


102

Mockito - Saya memahami mata-mata memanggil metode nyata pada suatu objek, sementara metode panggilan tiruan pada objek ganda. Mata-mata juga harus dihindari kecuali ada bau kode. Namun, bagaimana cara kerja mata-mata dan kapan saya harus menggunakannya? Bagaimana mereka berbeda dari tiruan?


2
kemungkinan duplikat dari tiruan tiruan vs. mata
rds

Jawaban:


91

Secara teknis, baik "pengejek" dan "mata-mata" adalah jenis khusus dari "tes ganda".

Mockito sayangnya membuat perbedaannya menjadi aneh.

Maket dalam mockito adalah tiruan normal dalam framework tiruan lainnya (memungkinkan Anda menghentikan pemanggilan; yaitu, mengembalikan nilai tertentu dari panggilan metode).

Mata-mata di mockito adalah tiruan parsial dalam kerangka kerja tiruan lainnya (bagian dari objek akan diejek dan sebagian akan menggunakan pemanggilan metode nyata).


43

Keduanya dapat digunakan untuk meniru metode atau bidang. Perbedaannya adalah bahwa dalam tiruan, Anda membuat objek tiruan atau palsu lengkap sementara dalam mata-mata, ada objek nyata dan Anda hanya memata-matai atau menghentikan metode tertentu darinya.

Sementara di objek mata-mata, tentu saja, karena ini adalah metode nyata, ketika Anda tidak menghentikan metode tersebut, maka metode itu akan memanggil perilaku metode nyata. Jika Anda ingin mengubah dan mengejek metode ini, Anda perlu menghentikannya.

Perhatikan contoh di bawah ini sebagai perbandingan.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}

Kapan Anda harus menggunakan tiruan atau mata-mata? Jika Anda ingin aman dan menghindari panggilan layanan eksternal dan hanya ingin menguji logika di dalam unit, gunakan tiruan. Jika Anda ingin memanggil layanan eksternal dan melakukan panggilan ketergantungan nyata, atau hanya mengatakan, Anda ingin menjalankan program apa adanya dan hanya menghentikan metode tertentu, kemudian gunakan mata-mata. Jadi itulah perbedaan antara mata-mata dan tiruan di mockito.


Jawaban yang bagus tetapi akan menampilkan verifikasi () pada kesalahan hanya tiruan dan tidak akan menjalankan pengujian kecuali Anda menginisialisasi daftar Anda dalam metode @Before setUp () seperti di sini mockList = mock (ArrayList.class); spyList = spy (ArrayList.class); dan hapus anotasi tiruan dan mata-mata yang disarankan di sini. Saya telah mengujinya dan ujian saya sudah lulus sekarang.
The_Martian

17

TL; versi DR,

Dengan tiruan , ini menciptakan contoh cangkang tanpa tulang untuk Anda.

List<String> mockList = Mockito.mock(ArrayList.class);

Dengan mata-mata, Anda dapat mengejek sebagian dari instance yang ada

List<String> spyList = Mockito.spy(new ArrayList<String>());

Kasus penggunaan tipikal untuk Spy: kelas memiliki konstruktor berparameter, Anda ingin membuat objek terlebih dahulu.


14

Saya telah membuat contoh yang dapat dijalankan di sini https://www.surasint.com/mockito-with-spy/

Saya menyalin sebagian di sini.

Jika Anda memiliki sesuatu seperti kode ini:

public void transfer( DepositMoneyService depositMoneyService, 
                      WithdrawMoneyService withdrawMoneyService, 
                      double amount, String fromAccount, String toAccount) {
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Anda mungkin tidak perlu memata-matai karena Anda hanya dapat meniru DepositMoneyService dan WithdrawMoneyService.

Tetapi dengan beberapa kode lama, ketergantungan ada pada kode seperti ini:

    public void transfer(String fromAccount, String toAccount, double amount) {
        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();
        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Ya, Anda dapat mengubah ke kode pertama tetapi kemudian API diubah. Jika metode ini digunakan di banyak tempat, Anda harus mengubah semuanya.

Alternatifnya adalah Anda dapat mengekstrak dependensi seperti ini:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();
        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Kemudian Anda dapat menggunakan mata-mata untuk menyuntikkan ketergantungan seperti ini:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target)
            .proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target)
            .proxyWithdrawMoneyServiceCreator();

Lebih detail ada di tautan di atas.


13

Tempat terbaik untuk memulai mungkin adalah dokumen untuk mockito .

Pada catatan umum, mockito mock memungkinkan Anda membuat rintisan.

Anda akan membuat metode rintisan jika, misalnya, metode tersebut melakukan operasi yang mahal. Katakanlah, ia mendapat koneksi database, mengambil nilai dari database dan mengembalikannya ke pemanggil. Mendapatkan koneksi db mungkin memerlukan waktu 30 detik, memperlambat eksekusi pengujian ke titik di mana Anda kemungkinan besar akan beralih konteks (atau berhenti menjalankan pengujian).

Jika logika yang Anda uji tidak peduli dengan koneksi database, maka Anda dapat mengganti metode itu dengan stub yang mengembalikan nilai hard code.

Mata-mata mockito memungkinkan Anda memeriksa apakah suatu metode memanggil metode lain. Ini bisa sangat berguna saat mencoba menguji kode lama.

Berguna jika Anda menguji metode yang bekerja melalui efek samping, maka Anda akan menggunakan mata-mata tiruan. Ini mendelegasikan panggilan ke objek nyata dan memungkinkan Anda untuk memverifikasi pemanggilan metode, berapa kali dipanggil, dll.


7

Pendeknya:

@Spydan @Mocksering digunakan dalam pengujian kode, tetapi pengembang bingung dalam kasus kapan harus menggunakan salah satunya dan dengan demikian pengembang akhirnya menggunakan @Mockagar aman.

  • Gunakan @Mockjika Anda ingin menguji fungsionalitas secara eksternal tanpa benar-benar memanggil metode itu.
  • Gunakan @Spyketika Anda ingin menguji fungsionalitas secara eksternal + internal dengan metode yang dipanggil.

Di bawah ini adalah contoh di mana saya telah mengambil skenario Election20xx di Amerika.

Pemilih dapat dibagi menurut VotersOfBelow21danVotersOfABove21 .

Exit poll yang ideal mengatakan bahwa Trump akan memenangkan pemilihan karena VotersOfBelow21dan VotersOfABove21keduanya akan memilih truf dengan mengatakan " Kami memilih Presiden Trump "

Tapi ini bukan skenario sebenarnya:

Pemilih dari kedua kelompok usia memilih Trump karena mereka tidak punya pilihan efektif lain selain Trump.

Jadi bagaimana cara mengujinya ??

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}

Sekarang Catatan di dua kelas pertama di atas, kedua kelompok usia orang mengatakan bahwa mereka tidak memiliki pilihan yang lebih baik daripada truf. Yang secara eksplisit berarti bahwa mereka memilih Trump hanya karena mereka tidak punya pilihan.

Sekarang ElectionOfYear20XX dikatakan bahwa Trump menang karena kedua kelompok umur sangat memilihnya.

Jika kita Menguji ElectionOfYear20XX mengujinya dengan @Mock, maka kami mungkin tidak bisa mendapatkan alasan sebenarnya mengapa Trump menang, kami hanya akan menguji alasan eksternal.

Jika kita mengujinya ElectionOfYear20XXdengan @Spy, maka kita mendapatkan alasan sebenarnya mengapa Trump menang dengan hasil exit poll eksternal, yaitu secara internal + eksternal.


ELectionOfYear20XX_TestKelas kami :

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Ini harus mengeluarkan hanya hasil tes logika yaitu pemeriksaan eksternal:

We elected President Trump 
We elected President Trump 

Menguji dengan @Spyeksternal maupun internal dengan pemanggilan metode aktual.

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Keluaran:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 

6

Saya suka kesederhanaan dari rekomendasi ini:

  • Jika Anda ingin aman dan menghindari panggilan layanan eksternal dan hanya ingin menguji logika di dalam unit, gunakan tiruan .
  • Jika Anda ingin memanggil layanan eksternal dan melakukan panggilan dependensi nyata, atau hanya mengatakan, Anda ingin menjalankan program apa adanya dan hanya menghentikan metode tertentu, kemudian gunakan spy .

Sumber: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

Perbedaan yang umum adalah:

  • Jika Anda ingin langsung men-stub metode dependensi, maka tiru dependensi itu.
  • Jika Anda ingin menghentikan data dalam sebuah ketergantungan sehingga semua metodenya mengembalikan nilai pengujian yang Anda butuhkan, maka mata - matai ketergantungan itu.

Perhatikan bahwa Spy dan Mock selalu dimaksudkan untuk dependensi, dan bukan untuk sistem yang diuji.
leo9r
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.