Cara mengejek kelas terakhir dengan mockito


218

Saya memiliki kelas terakhir, sesuatu seperti ini:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Saya menggunakan kelas ini di beberapa kelas lain seperti ini:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

dan di kelas tes JUnit saya karena Seasons.javasaya ingin mengejek RainOnTreeskelas. Bagaimana saya bisa melakukan ini dengan Mockito?


9
Mockito tidak mengizinkannya, namun PowerMock tidak.
fge

1
Pada Mockito 2.x, Mockito sekarang mendukung mengejek kelas akhir dan metode.
Kent

Jawaban:


155

Mengejek final / kelas statis / metode hanya dimungkinkan dengan Mockito v2.

tambahkan ini dalam file gradle Anda:

testImplementation 'org.mockito:mockito-inline:2.13.0'

Ini tidak mungkin dengan Mockito v1, dari FAQ Mockito :

Apa keterbatasan Mockito

  • Membutuhkan java 1.5+

  • Tidak bisa mengejek kelas akhir

...


2
Ini tidak berfungsi untuk saya di Scala (dengan modifikasi sbt).
micseydel

2
Ini tidak cukup bagi saya. Saya juga harus membuat src / test / resource / mockito-extensions / org.mockito.plugins.MockMaker dengan "mock-maker-inline" di dalamnya sesuai baeldung.com/mockito-final
micseydel

204

Mockito 2 sekarang mendukung kelas dan metode akhir !

Tetapi untuk sekarang itu fitur "inkubasi". Dibutuhkan beberapa langkah untuk mengaktifkannya yang dijelaskan dalam What's New in Mockito 2 :

Mengejek kelas akhir dan metode adalah fitur inkubasi , opt-in. Ini menggunakan kombinasi instrumentasi agen Java dan subclassing untuk memungkinkan mockability jenis ini. Karena ini bekerja secara berbeda dengan mekanisme kami saat ini dan ini memiliki keterbatasan yang berbeda dan karena kami ingin mengumpulkan pengalaman dan umpan balik pengguna, fitur ini harus diaktifkan secara eksplisit agar tersedia; itu dapat dilakukan melalui mekanisme ekstensi mockito dengan membuat file yang src/test/resources/mockito-extensions/org.mockito.plugins.MockMakerberisi satu baris:

mock-maker-inline

Setelah Anda membuat file ini, Mockito akan secara otomatis menggunakan mesin baru ini dan Anda dapat melakukannya:

 final class FinalClass {
   final String finalMethod() { return "something"; }
 }

 FinalClass concrete = new FinalClass(); 

 FinalClass mock = mock(FinalClass.class);
 given(mock.finalMethod()).willReturn("not anymore");

 assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

Dalam tonggak berikutnya, tim akan membawa cara terprogram untuk menggunakan fitur ini. Kami akan mengidentifikasi dan memberikan dukungan untuk semua skenario yang tidak dapat digerakkan. Tetap disini dan beri tahu kami pendapat Anda tentang fitur ini!


14
Saya masih mendapatkan kesalahan: Tidak dapat mengejek / memata-matai kelas android.content.ComponentName Mockito tidak dapat mengejek / memata-matai karena: - kelas terakhir
IgorGanapolsky

3
Pastikan Anda meletakkan org.mockito.plugins.MockMakerfile di folder yang benar.
WindRider

7
Saya juga mendapatkan kesalahan bahkan setelah mengikuti yang disebutkan di atas: Mockito tidak dapat mengejek / memata-matai karena: - kelas akhir
rcde0

8
@vCillusion jawaban ini tidak terkait dengan PowerMock dengan cara apa pun.
Jalur

6
Saya mengikuti instruksi ini tetapi saya masih tidak bisa melakukan ini, apakah ada yang harus melakukan hal lain?
Franco

43

Anda tidak dapat mengejek kelas terakhir dengan Mockito, karena Anda tidak dapat melakukannya sendiri.

Apa yang saya lakukan, adalah membuat kelas non-final untuk membungkus kelas akhir dan digunakan sebagai delegasi. Contoh dari ini adalah TwitterFactorykelas, dan ini adalah kelas saya yang bisa dipermainkan:

public class TwitterFactory {

    private final twitter4j.TwitterFactory factory;

    public TwitterFactory() {
        factory = new twitter4j.TwitterFactory();
    }

    public Twitter getInstance(User user) {
        return factory.getInstance(accessToken(user));
    }

    private AccessToken accessToken(User user) {
        return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
    }

    public Twitter getInstance() {
        return factory.getInstance();
    }
}

Kerugiannya adalah ada banyak kode boilerplate; keuntungannya adalah Anda dapat menambahkan beberapa metode yang mungkin terkait dengan bisnis aplikasi Anda (seperti getInstance yang mengambil pengguna alih-alih accessToken, dalam kasus di atas).

Dalam kasus Anda, saya akan membuat kelas non-final RainOnTreesyang mendelegasikan ke kelas akhir. Atau, jika Anda bisa membuatnya non-final, itu akan lebih baik.


6
+1. Jika diinginkan, Anda dapat menggunakan sesuatu seperti Lombok @Delegateuntuk menangani banyak pelat ketel.
ruakh

2
@luigi dapatkah Anda menambahkan cuplikan kode untuk Junit sebagai Contoh. Saya mencoba membuat Wrapper untuk kelas terakhir saya, tetapi tidak bisa keluar, bagaimana mengujinya.
Luar Biasa

31

tambahkan ini dalam file gradle Anda:

testImplementation 'org.mockito:mockito-inline:2.13.0'

ini adalah konfigurasi untuk membuat mockito berfungsi dengan kelas akhir


1
Seharusnya mungkin menggunakan "testImplementation" sekarang alih-alih "testCompile". Gradle tidak menyukai "testCompile" lagi.
jwehrle

komentar yang bagus, terima kasih! diedit ke testImplementation. komentar asli: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP

2
Ini menghasilkan kesalahan saat berjalan di Linux / OpenJDK 1.8:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa

Bekerja dengan baik ketika beralih ke Oracle JDK 1.8
naXa

23

Gunakan Powermock. Tautan ini menunjukkan, bagaimana melakukannya: https://github.com/jayway/powermock/wiki/MockFinal


30
Saya pikir PowerMock seperti salah satu dari obat-obatan yang seharusnya hanya digunakan dengan resep dokter. Dalam artian: seseorang harus menjelaskan bahwa PowerMock memiliki banyak masalah; dan menggunakan itu seperti jalan terakhir; dan harus dihindari sebisa mungkin.
GhostCat

1
mengapa kamu mengatakan itu?
PragmaticProgrammer

Saya menggunakan Powermockuntuk mengejek kelas akhir dan metode statis untuk meningkatkan cakupan saya yang secara resmi diperiksa Sonarqube. Cakupan adalah 0% sejak SonarQube, untuk alasan apa pun tidak mengenali kelas yang menggunakan Powermock di mana pun di dalamnya. Saya membawa saya dan tim saya beberapa waktu untuk menyadarinya dari beberapa utas online. Jadi itu hanyalah salah satu alasan untuk berhati-hati dengan Powermock dan mungkin tidak menggunakannya.
amer

16

Hanya untuk menindaklanjuti. Silakan tambahkan baris ini ke file gradle Anda:

testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'

Saya sudah mencoba berbagai versi mockito-core dan mockito-all. Tak satu pun dari mereka bekerja.


1
Untuk menambah ini, satu hal yang saya amati adalah bahwa jika Anda menggunakan Powermock bersama dengan mockito; kemudian menambahkan file plugin mockmaker di 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' tidak akan berguna dalam mengejek kelas akhir. Sebaliknya, menambahkan ketergantungan seperti yang disebutkan oleh Michael_Zhang di atas akan menyelesaikan masalah mengejek kelas akhir. Juga, pastikan Anda menggunakan Mockito 2 alih-alih Mockito1
dishooom

12

Saya kira Anda membuatnya finalkarena Anda ingin mencegah kelas lain dari perluasan RainOnTrees. Seperti yang disarankan oleh Java Efektif (item 15), ada cara lain untuk menjaga kelas tetap dekat dengan ekstensi tanpa membuatnya final:

  1. Hapus finalkata kunci;

  2. Buat konstruktornya private. Tidak ada kelas yang dapat memperpanjangnya karena tidak akan dapat memanggil superkonstruktor;

  3. Buat metode pabrik statis untuk membuat instance kelas Anda.

    // No more final keyword here.
    public class RainOnTrees {
    
        public static RainOnTrees newInstance() {
            return new RainOnTrees();
        }
    
    
        private RainOnTrees() {
            // Private constructor.
        }
    
        public void startRain() {
    
            // some code here
        }
    }

Dengan menggunakan strategi ini, Anda akan dapat menggunakan Mockito dan menutup kelas Anda untuk ekstensi dengan sedikit kode boilerplate.


1
ini tidak berfungsi untuk metode final yang dengan mockito 2 juga dapat diejek.
Łukasz Rzeszotarski

11

Saya memiliki masalah yang sama. Karena kelas yang saya coba mengejek adalah kelas yang sederhana, saya hanya membuat instance dan mengembalikannya.


2
Tentu saja, mengapa mengejek kelas sederhana? Mengejek adalah untuk interaksi 'mahal': layanan lain, mesin, kelas data dll.
StripLight

3
Jika Anda membuat instance dari itu, Anda tidak dapat menerapkan metode Mockito.verify setelahnya. Penggunaan utama mock adalah untuk dapat menguji beberapa metodenya.
riroo

6

Cobalah ini:

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

Ini berhasil untuk saya. "SomeMockableType.class" adalah kelas induk dari apa yang Anda ingin tiru atau mata-mata, dan beberapaInstanceThatIsNotMockableOrSpyable adalah kelas aktual yang Anda ingin tiru atau mata-mata.

Untuk lebih jelasnya lihat di sini


3
Perlu diperhatikan bahwa para delegasi sangat berbeda dengan ejekan mata-mata asli. Dalam mata-mata mockito asli, "this" dalam contoh merujuk ke mata-mata itu sendiri (karena menggunakan subkelas) Namun, dalam delegasi, "ini" akan menjadi objek nyata. Bukan mata-mata. Dengan demikian, tidak ada cara untuk melakukanReturn / verifikasi untuk fungsi self-calling.
Dennis C

1
dapatkah Anda memberikan contoh?
Vishwa Ratna

5

Solusi lain, yang mungkin berlaku dalam beberapa kasus, adalah membuat antarmuka yang diimplementasikan oleh kelas akhir itu, mengubah kode untuk menggunakan antarmuka, bukan kelas beton dan kemudian mengejek antarmuka. Ini memungkinkan Anda memisahkan kontrak (antarmuka) dari implementasi (kelas akhir). Tentu saja, jika yang Anda inginkan benar-benar mengikat ke kelas akhir, ini tidak akan berlaku.


5

Sebenarnya ada satu cara, yang saya gunakan untuk memata-matai. Ini akan bekerja untuk Anda hanya jika dua prasyarat dipenuhi:

  1. Anda menggunakan semacam DI untuk menyuntikkan instance kelas akhir
  2. Kelas akhir mengimplementasikan antarmuka

Harap ingat Butir 16 dari Java Efektif . Anda dapat membuat pembungkus (bukan final) dan meneruskan semua panggilan ke turunan dari kelas akhir:

public final class RainOnTrees implement IRainOnTrees {
    @Override public void startRain() { // some code here }
}

public class RainOnTreesWrapper implement IRainOnTrees {
    private IRainOnTrees delegate;
    public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
    @Override public void startRain() { delegate.startRain(); }
}

Sekarang Anda tidak hanya dapat mengejek kelas akhir Anda tetapi juga memata-matai:

public class Seasons{
    RainOnTrees rain;
    public Seasons(IRainOnTrees rain) { this.rain = rain; };
    public void findSeasonAndRain(){
        rain.startRain();
   }
}

IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();

5

Di Mockito 3 dan lebih banyak lagi saya memiliki masalah yang sama dan memperbaikinya dari tautan ini

Mock Kelas Akhir dan Metode dengan Mockito sebagai berikut

Sebelum Mockito dapat digunakan untuk mengejek kelas dan metode final, ia perlu> dikonfigurasikan.

Kita perlu menambahkan file teks ke direktori src / test / resources / mockito-extensions proyek bernama org.mockito.plugins.MockMaker dan menambahkan satu baris teks:

mock-maker-inline

Mockito memeriksa direktori ekstensi untuk file-file konfigurasi ketika itu dimuat. File ini memungkinkan mengejek metode dan kelas akhir.


4

Penghemat waktu untuk orang-orang yang menghadapi masalah yang sama (Mockito + Kelas Akhir) di Android + Kotlin. Seperti dalam kelas Kotlin adalah final secara default. Saya menemukan solusi di salah satu sampel Android Google dengan komponen Arsitektur. Solusi dipilih dari sini: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Buat anotasi berikut:

/**
 * This annotation allows us to open some classes for mocking purposes while they are final in
 * release builds.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass

/**
 * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
 */
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting

Ubah file gradle Anda. Ambil contoh dari sini: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

apply plugin: 'kotlin-allopen'

allOpen {
    // allows mocking for classes w/o directly opening them for release builds
    annotation 'com.android.example.github.testing.OpenClass'
}

Sekarang Anda dapat membuat anotasi kelas apa saja untuk membuatnya terbuka untuk pengujian:

@OpenForTesting
class RepoRepository 

Ini berfungsi baik di build.gradle tingkat aplikasi tetapi apa yang bisa kita lakukan untuk mendapatkan ini di tingkat perpustakaan?
Sumit T

Bisakah Anda sedikit menjelaskan? Biasanya, gunakan pola fasad untuk terhubung ke lib. Dan mengejek kelas fasad ini untuk menguji aplikasi. Dengan cara ini kita tidak perlu mengejek kelas lib.
Ozeetee

3

Ini dapat dilakukan jika Anda menggunakan Mockito2, dengan fitur inkubasi baru yang mendukung mengejek kelas akhir & metode.

Poin-poin penting yang perlu diperhatikan:
1. Buat file sederhana dengan nama "org.mockito.plugins.MockMaker" dan letakkan di folder bernama "mockito-extensions". Folder ini harus tersedia di classpath.
2. Konten file yang dibuat di atas harus satu baris seperti yang diberikan di bawah ini:
mock-maker-inline

Dua langkah di atas diperlukan untuk mengaktifkan mekanisme ekstensi mockito dan menggunakan fitur opt-in ini.

Kelas sampel adalah sebagai berikut: -

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println("Final class says Hello!!!");
    return "0";
}

}

Foo.java

public class Foo {

public String executeFinal(FinalClass finalClass){
    return finalClass.hello();
}

}

FooTest.java

public class FooTest {

@Test
public void testFinalClass(){
    // Instantiate the class under test.
    Foo foo = new Foo();

    // Instantiate the external dependency
    FinalClass realFinalClass = new FinalClass();

    // Create mock object for the final class. 
    FinalClass mockedFinalClass = mock(FinalClass.class);

    // Provide stub for mocked object.
    when(mockedFinalClass.hello()).thenReturn("1");

    // assert
    assertEquals("0", foo.executeFinal(realFinalClass));
    assertEquals("1", foo.executeFinal(mockedFinalClass));

}

}

Semoga ini bisa membantu.

Artikel lengkap hadir di sini mengejek-unmockable .


Anda harus memasukkan jawabannya di sini dan tidak menautkan ke situs eksternal. Jika prosedurnya panjang, Anda dapat menyertakan ikhtisar.
rghome

harap pastikan penjelasan di bawah ini digunakan ketika mengejek @RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class})
vCillusion

1
@vCillusion - Contoh yang saya tunjukkan hanya menggunakan API Mockito2. Menggunakan fitur opt-in dari Mockito2, orang dapat mengejek kelas akhir secara langsung tanpa perlu menggunakan Powermock.
ksl

2

Ya masalah yang sama di sini, kita tidak bisa mengejek kelas terakhir dengan Mockito. Agar akurat, Mockito tidak dapat mengejek / memata-matai berikut:

  • kelas akhir
  • kelas anonim
  • tipe primitif

Tetapi menggunakan kelas pembungkus menurut saya harga yang harus dibayar, jadi dapatkan PowerMockito sebagai gantinya.


2

Saya pikir Anda perlu berpikir lebih pada prinsipnya. Sebagai gantinya Anda kelas akhir menggunakan antarmuka dan antarmuka tiruan sebagai gantinya.

Untuk ini:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

Menambahkan

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

dan mengejek antarmuka Anda:

 @Before
    fun setUp() {
        rainService= Mockito.mock(iRainOnTrees::class.java)

        `when`(rainService.startRain()).thenReturn(
            just(true).delay(3, TimeUnit.SECONDS)
        )

    }

1

Silakan lihat JMockit . Ini memiliki dokumentasi yang luas dengan banyak contoh. Di sini Anda memiliki contoh solusi dari masalah Anda (untuk menyederhanakan saya telah menambahkan konstruktor Seasonsuntuk menyuntikkan RainOnTreescontoh mocked ):

package jmockitexample;

import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class SeasonsTest {

    @Test
    public void shouldStartRain(@Mocked final RainOnTrees rain) {
        Seasons seasons = new Seasons(rain);

        seasons.findSeasonAndRain();

        new Verifications() {{
            rain.startRain();
        }};
    }

    public final class RainOnTrees {
        public void startRain() {
            // some code here
        }

    }

    public class Seasons {

        private final RainOnTrees rain;

        public Seasons(RainOnTrees rain) {
            this.rain = rain;
        }

        public void findSeasonAndRain() {
            rain.startRain();
        }

    }
}

1

Solusi yang diberikan oleh RC dan Luigi R. Viggiano bersama mungkin adalah ide terbaik.

Meskipun Mockito tidak bisa , dengan desain, mengejek kelas akhir, pendekatan delegasi dimungkinkan . Ini memiliki kelebihan:

  1. Anda tidak dipaksa untuk mengubah kelas Anda menjadi non-final jika itu yang menjadi tujuan API Anda (kelas akhir memiliki manfaatnya ).
  2. Anda sedang menguji kemungkinan dekorasi di sekitar API Anda.

Dalam kasus pengujian Anda, Anda dengan sengaja meneruskan panggilan ke sistem yang sedang diuji. Karenanya, secara desain, dekorasi Anda tidak melakukan apa-apa.

Karenanya, Anda menguji juga dapat menunjukkan bahwa pengguna hanya dapat menghias API alih-alih memperpanjangnya.

Pada catatan yang lebih subyektif: Saya lebih suka menjaga kerangka kerja seminimal mungkin, itulah sebabnya JUnit dan Mockito biasanya cukup untuk saya. Bahkan, dengan membatasi cara ini kadang-kadang memaksa saya untuk refactor untuk selamanya juga.


1

Jika Anda mencoba menjalankan unit-test di bawah folder tes , solusi teratas baik-baik saja. Cukup ikuti itu menambahkan ekstensi.

Tetapi jika Anda ingin menjalankannya dengan kelas terkait Android seperti konteks atau aktivitas yang berada di bawah folder androidtest , jawabannya adalah untuk Anda.


1

Tambahkan dependensi ini untuk menjalankan mockito dengan sukses:

testImplementation 'org.mockito: mockito-core: 2.24.5'
testImplementation "org.mockito: mockito-inline: 2.24.5"


0

Seperti yang telah dinyatakan orang lain, ini tidak akan berhasil dengan Mockito. Saya akan menyarankan menggunakan refleksi untuk mengatur bidang spesifik pada objek yang sedang digunakan oleh kode yang diuji. Jika Anda sering melakukan hal ini, Anda dapat membungkus fungsi ini di perpustakaan.

Sebagai tambahan, jika Anda adalah satu-satunya yang menandai kelas final, berhentilah melakukannya. Saya mengalami pertanyaan ini karena saya bekerja dengan API di mana semuanya ditandai sebagai final untuk mencegah kebutuhan sah saya untuk ekstensi (mengejek), dan saya berharap bahwa pengembang tidak berasumsi bahwa saya tidak akan pernah perlu memperluas kelas.


1
Kelas API publik harus terbuka untuk ekstensi. Sepenuhnya setuju. Namun, dalam basis kode pribadi, finalharus menjadi default.
ErikE

0

Bagi kami, itu karena kami mengecualikan mockito-inline dari koin-test. Satu modul gradle sebenarnya membutuhkan ini dan karena alasan hanya gagal pada rilis build (debug build di IDE berfungsi) :-P


0

Untuk kelas akhir, tambahkan di bawah ini untuk mengejek dan memanggil statis atau non-statis.

1- tambahkan ini di tingkat kelas @SuppressStatucInitializationFor (value = {nama kelas dengan paket})
2- PowerMockito.mockStatic (classname.class) akan mengolok-olok kelas
3- lalu gunakan pernyataan when Anda untuk mengembalikan objek tiruan saat memanggil metode kelas ini.

Nikmati


-5

Tidak mencoba final, tetapi untuk pribadi, menggunakan refleksi menghapus pengubah bekerja! sudah diperiksa lebih lanjut, tidak berfungsi untuk final.


ini tidak menjawab pertanyaan yang diajukan
Sanjit Kumar Mishra
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.