Bagaimana cara kerja pemanggilan mockito when ()?

111

Diberikan pernyataan Mockito berikut:

when(mock.method()).thenReturn(someValue);

Bagaimana cara Mockito membuat proksi sesuatu untuk tiruan, mengingat pernyataan mock.method () akan meneruskan nilai kembalian ke when ()? Saya membayangkan bahwa ini menggunakan beberapa barang CGLib, tetapi akan tertarik untuk mengetahui bagaimana ini dilakukan secara teknis.

marchaos
sumber

Jawaban:

118

Jawaban singkatnya adalah bahwa dalam contoh Anda, hasil dari mock.method()akan berupa nilai kosong yang sesuai dengan tipe; mockito menggunakan tipuan melalui proksi, intersepsi metode, dan instance MockingProgresskelas yang dibagikan untuk menentukan apakah pemanggilan metode pada tiruan adalah untuk menghentikan atau memutar ulang perilaku stub yang ada daripada menyampaikan informasi tentang stubbing melalui nilai kembalian dari metode yang diejek.

Analisis mini dalam beberapa menit melihat kode mockito adalah sebagai berikut. Perhatikan, ini adalah deskripsi yang sangat kasar - ada banyak detail yang dimainkan di sini. Saya sarankan Anda memeriksa sendiri sumber di github .

Pertama, saat Anda memalsukan kelas menggunakan mockmetode Mockitokelas, pada dasarnya inilah yang terjadi:

  1. Mockito.mockmendelegasikan ke org.mockito.internal.MockitoCore.mock, meneruskan pengaturan tiruan default sebagai parameter.
  2. MockitoCore.mockdelegasi ke org.mockito.internal.util.MockUtil.createMock
  3. The MockUtilkelas menggunakan ClassPathLoaderkelas untuk mendapatkan contoh MockMakergunakan untuk membuat tiruan tersebut. Secara default, kelas CgLibMockMaker digunakan.
  4. CgLibMockMakermenggunakan kelas yang dipinjam dari JMock, ClassImposterizeryang menangani pembuatan tiruan. Potongan kunci dari 'sihir mockito' yang digunakan adalah yang MethodInterceptordigunakan untuk membuat tiruan: mockito MethodInterceptorFilter, dan rangkaian instance MockHandler, termasuk instance MockHandlerImpl . Interceptor metode meneruskan pemanggilan ke instance MockHandlerImpl, yang mengimplementasikan logika bisnis yang harus diterapkan ketika metode dipanggil pada tiruan (yaitu, mencari untuk melihat apakah jawaban sudah direkam, menentukan apakah pemanggilan tersebut merupakan rintisan baru, dll. Status defaultnya adalah jika stub belum terdaftar untuk metode yang dipanggil, nilai kosong yang sesuai dengan tipe akan dikembalikan.

Sekarang, mari kita lihat kode dalam contoh Anda:

when(mock.method()).thenReturn(someValue)

Berikut adalah urutan kode ini akan dijalankan:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

Kunci untuk memahami apa yang sedang terjadi adalah apa yang terjadi ketika metode pada tiruan dipanggil: metode interseptor memberikan informasi tentang pemanggilan metode, dan mendelegasikan ke rantai MockHandlerinstance-nya, yang akhirnya didelegasikan MockHandlerImpl#handle. Selama MockHandlerImpl#handle, penangan tiruan membuat instance dari OngoingStubbingImpldan meneruskannya ke MockingProgressinstance bersama .

Ketika whenmetode dipanggil setelah pemanggilan method(), itu mendelegasikan ke MockitoCore.when, yang memanggil stub()metode kelas yang sama. Metode ini membongkar stub yang sedang berlangsung dari MockingProgressinstance bersama yang menjadi tujuan method()penulisan pemanggilan palsu, dan mengembalikannya. Kemudian thenReturnmetode kemudian dipanggil pada OngoingStubbinginstance.

Paul Morie
sumber
1
Terima kasih atas respon yang mendetail. Pertanyaan lain - Anda menyebutkan bahwa "ketika metode dipanggil setelah pemanggilan metode ()" - bagaimana cara mengetahui bahwa pemanggilan when () adalah pemanggilan berikutnya (atau membungkus) pemanggilan metode ()? Harapan itu masuk akal.
marchaos
@marchaos Tidak tahu. Dengan when(mock.method()).thenXyz(...)sintaks, mock.method()dijalankan dalam mode "replay", bukan dalam mode "stubbing". Biasanya, eksekusi ini mock.method()tidak berpengaruh, sehingga kemudian ketika thenXyz(...)( thenReturn, thenThrow, thenAnswer, dll) dijalankan, itu masuk ke "mematikan" mode dan kemudian mencatat hasil yang diinginkan untuk itu metode panggilan.
Rogério
1
Rogerio, sebenarnya sedikit lebih halus dari itu - mockito tidak memiliki mode stubbing dan replay eksplisit. Saya akan mengedit jawaban saya nanti agar lebih jelas.
Paul Morie
Saya akan menyimpulkan bahwa singkatnya, lebih mudah untuk mencegat panggilan metode dalam metode lain dengan CGLIB atau Javassist yang mencegat, katakanlah, operator "jika".
Infeligo
Saya belum memijat deskripsi saya di sini, tetapi saya juga belum melupakannya. FYI.
Paul Morie
33

Jawaban singkatnya adalah, di balik layar, Mockito menggunakan beberapa jenis variabel / penyimpanan global untuk menyimpan informasi langkah-langkah pembuatan rintisan metode (pemanggilan metode (), ketika (), kemudianReturn () dalam contoh Anda), sehingga pada akhirnya dapat membangun peta tentang apa yang harus dikembalikan ketika apa yang disebut pada apa param.

Saya menemukan artikel ini sangat membantu: Penjelasan cara kerja Kerangka Mock berbasis proxy ( http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html ). Penulis menerapkan kerangka kerja Mocking demonstrasi, yang menurut saya merupakan sumber yang sangat bagus untuk orang-orang yang ingin mengetahui bagaimana kerangka kerja Mocking ini bekerja.

Menurut pendapat saya, ini adalah penggunaan Anti-Pattern yang khas. Biasanya kita harus menghindari 'efek samping' ketika kita mengimplementasikan sebuah metode, yang berarti metode tersebut harus menerima masukan dan melakukan beberapa kalkulasi dan mengembalikan hasilnya - tidak ada yang berubah selain itu. Tapi Mockito sengaja melanggar aturan itu. Metodenya menyimpan banyak info selain mengembalikan hasilnya: Mockito.anyString (), mockInstance.method (), when (), thenReturn, semuanya memiliki 'efek samping' khusus. Itu juga mengapa kerangka kerja terlihat seperti keajaiban pada pandangan pertama - kami biasanya tidak menulis kode seperti itu. Namun, dalam kasus kerangka kerja tiruan, desain anti-pola ini adalah desain yang bagus, karena mengarah ke API yang sangat sederhana.

Jacob Wu
sumber
4
Tautan luar biasa. Jenius di balik ini adalah: API yang sangat sederhana yang membuat semuanya terlihat sangat bagus. Keputusan bagus lainnya adalah metode when () menggunakan generik sehingga metode thenReturn () adalah tipe yang aman.
David Tonhofer
Saya menganggap ini jawaban yang lebih baik. Berbeda dengan jawaban lain, ini menjelaskan dengan jelas konsep proses mocking alih-alih aliran kontrol melalui kode konkret. Saya setuju, tautan yang sangat bagus.
mihca