Menginisialisasi objek tiruan - MockIto

122

Ada banyak cara untuk menginisialisasi objek tiruan menggunakan MockIto. Apa cara terbaik di antara ini?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2.

@RunWith(MockitoJUnitRunner.class)

[EDIT] 3.

mock(XXX.class);

sarankan saya jika ada cara lain yang lebih baik dari ini ...

VinayVeluri
sumber

Jawaban:

153

Untuk inisialisasi tiruan , menggunakan pelari atau solusi MockitoAnnotations.initMocksyang benar-benar setara. Dari javadoc dari MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


Solusi pertama (dengan MockitoAnnotations.initMocks) dapat digunakan ketika Anda telah mengkonfigurasi pelari tertentu (SpringJUnit4ClassRunner misalnya) pada kasus pengujian Anda.

Solusi kedua (dengan MockitoJUnitRunner) adalah yang lebih klasik dan favorit saya. Kodenya lebih sederhana. Menggunakan runner memberikan keuntungan besar dari validasi otomatis penggunaan framework (dijelaskan oleh @David Wallace dalam jawaban ini ).

Kedua solusi memungkinkan untuk berbagi tiruan (dan mata-mata) di antara metode pengujian. Ditambah dengan @InjectMocks, mereka memungkinkan untuk menulis tes unit dengan sangat cepat. Kode tiruan boilerplate dikurangi, pengujian lebih mudah dibaca. Sebagai contoh:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Kelebihan: Kodenya minimal

Kekurangan: Ilmu hitam. IMO ini terutama karena anotasi @InjectMocks. Dengan anotasi ini "Anda kehilangan rasa sakit kode" (lihat komentar bagus @Brice )


Solusi ketiga adalah membuat tiruan Anda pada setiap metode pengujian. Ini memungkinkan seperti yang dijelaskan oleh @mlk dalam jawabannya untuk memiliki " tes mandiri ".

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Pro: Anda dengan jelas menunjukkan cara kerja api Anda (BDD ...)

Kekurangan: ada lebih banyak kode boilerplate. (Penciptaan mengejek)


Rekomendasi saya adalah kompromi. Gunakan @Mockanotasi dengan @RunWith(MockitoJUnitRunner.class), tapi jangan gunakan @InjectMocks:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Kelebihan: Anda dengan jelas menunjukkan bagaimana api Anda bekerja (How my ArticleManager is instantiated). Tidak ada kode boilerplate.

Cons: Tes ini tidak mandiri, lebih sedikit kesulitan kode

gontard
sumber
Berhati-hatilah, anotasi berguna tetapi tidak melindungi Anda dari membuat desain OO yang buruk (atau menurunkannya). Secara pribadi sementara saya senang mengurangi kode boilerplate, saya kehilangan rasa sakit kode (atau PITA) yang menjadi pemicu untuk mengubah desain ke yang lebih baik, jadi saya dan tim memperhatikan desain OO. Saya merasa bahwa mengikuti desain OO dengan prinsip seperti desain SOLID atau ide GOOS jauh lebih penting daripada memilih cara membuat contoh tiruan.
Brice
1
(tindak lanjut) Jika Anda tidak melihat bagaimana objek ini dibuat, Anda tidak akan merasa sakit karenanya, dan pemrogram masa depan mungkin tidak bereaksi dengan baik jika fungsionalitas baru harus ditambahkan. Pokoknya itu bisa diperdebatkan dua arah, saya hanya mengatakan untuk berhati-hati tentang itu.
Brice
6
TIDAK BENAR bahwa keduanya setara. TIDAK BENAR bahwa kode yang lebih sederhana adalah satu-satunya keuntungan untuk digunakan MockitoJUnitRunner. Untuk informasi lebih lanjut tentang perbedaan, lihat pertanyaan di stackoverflow.com/questions/10806345/… dan jawaban saya untuk itu.
Dawood ibn Kareem
2
@Gontard Ya tentu dependensi terlihat, tetapi saya telah melihat kode yang salah menggunakan pendekatan ini. Soal penggunaan Collaborator collab = mock(Collaborator.class), menurut saya cara ini tentunya merupakan pendekatan yang valid. Meskipun ini mungkin cenderung bertele-tele, Anda dapat memperoleh pemahaman dan refactorabilitas pengujian. Kedua cara memiliki pro dan kontra, saya belum memutuskan pendekatan mana yang lebih baik. Amyway selalu mungkin untuk menulis omong kosong, dan mungkin tergantung pada konteks dan pembuat kode.
Brice
1
@mlk saya setuju dengan Anda. Bahasa Inggris saya tidak terlalu bagus dan kurang bernuansa. Maksud saya adalah menekankan kata UNIT.
gontard
30

Sekarang ada (mulai v1.10.7) cara keempat untuk membuat contoh tiruan, yang menggunakan aturan JUnit4 yang disebut MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit mencari subclass TestRule yang dianotasi dengan @Rule , dan menggunakannya untuk menggabungkan Pernyataan pengujian yang disediakan Runner . Hasilnya adalah Anda dapat mengekstrak metode @Before, metode @After, dan bahkan mencoba ... menangkap pembungkus ke dalam aturan. Anda bahkan dapat berinteraksi dengan ini dari dalam pengujian Anda, seperti yang dilakukan ExpectedException .

MockitoRule berperilaku hampir persis seperti MockitoJUnitRunner , kecuali bahwa Anda dapat menggunakan pelari lain, seperti Parameterized (yang memungkinkan konstruktor pengujian Anda mengambil argumen sehingga pengujian Anda dapat dijalankan beberapa kali), atau runner pengujian Robolectric (sehingga classloadernya dapat menyediakan pengganti Java untuk kelas asli Android). Ini membuatnya lebih fleksibel untuk digunakan di versi JUnit dan Mockito terbaru.

Singkatnya:

  • Mockito.mock(): Doa langsung tanpa dukungan anotasi atau validasi penggunaan.
  • MockitoAnnotations.initMocks(this): Dukungan anotasi, tidak ada validasi penggunaan.
  • MockitoJUnitRunner: Dukungan anotasi dan validasi penggunaan, tetapi Anda harus menggunakan runner itu.
  • MockitoRule: Dukungan anotasi dan validasi penggunaan dengan pelari JUnit apa pun.

Lihat juga: Bagaimana JUnit @Rule bekerja?

Jeff Bowman
sumber
3
Di Kotlin, aturannya terlihat seperti ini:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan
10

Ada cara yang rapi untuk melakukan ini.

  • Jika ini adalah Tes Unit, Anda dapat melakukan ini:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
  • EDIT: Jika ini adalah tes Integrasi, Anda dapat melakukan ini (tidak dimaksudkan untuk digunakan seperti itu dengan Spring. Tunjukkan saja bahwa Anda dapat menginisialisasi tiruan dengan Pelari yang berbeda):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }
emd
sumber
1
Jika MOCK juga terlibat dalam tes Integrasi, apakah itu masuk akal?
VinayVeluri
2
sebenarnya tidak, hak Anda. Saya hanya ingin menunjukkan kemungkinan Mockito. Misalnya jika Anda menggunakan RESTFuse, Anda harus menggunakan runnernya sehingga Anda dapat menginisialisasi tiruan dengan MockitoAnnotations.initMocks (this);
emd
8

MockitoAnnotation & runner telah dibahas dengan baik di atas, jadi saya akan memberikan tuppence saya untuk yang tidak dicintai:

XXX mockedXxx = mock(XXX.class);

Saya menggunakan ini karena menurut saya ini sedikit lebih deskriptif dan saya lebih suka pengujian unit (bukan larangan yang benar) untuk tidak menggunakan variabel anggota karena saya suka pengujian saya (sebanyak mungkin) mandiri.

Michael Lloyd Lee mlk
sumber
Apakah ada keuntungan lain daripada menggunakan tiruan (XX.class) kecuali membuat kasus uji menjadi mandiri?
VinayVeluri
Tidak sejauh yang saya sadari.
Michael Lloyd Lee mlk
3
Lebih sedikit sihir yang harus dipahami untuk membaca ujian. Anda mendeklarasikan variabel, dan memberinya nilai - tidak ada anotasi, refleksi, dll.
Karu
2

Sebuah contoh kecil untuk JUnit 5 Jupiter, "RunWith" telah dihapus, Anda sekarang harus menggunakan Ekstensi menggunakan Anotasi "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}
fl0w
sumber
0

Jawaban lainnya bagus dan berisi lebih banyak detail jika Anda menginginkan / membutuhkannya.
Selain itu, saya ingin menambahkan TL; DR:

  1. Lebih suka menggunakan
    • @RunWith(MockitoJUnitRunner.class)
  2. Jika Anda tidak bisa (karena Anda sudah menggunakan pelari yang berbeda), lebih baik gunakan
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Mirip dengan (2), tetapi Anda tidak boleh menggunakan ini lagi:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Jika Anda ingin menggunakan tiruan di salah satu pengujian dan tidak ingin mengeksposnya ke pengujian lain di kelas pengujian yang sama, gunakan
    • X x = mock(X.class)

(1) dan (2) dan (3) saling eksklusif.
(4) dapat digunakan dalam kombinasi dengan yang lain.

perhubungan
sumber