Menggunakan Mockito untuk mengejek kelas dengan parameter generik


280

Apakah ada metode bersih mengejek kelas dengan parameter generik? Katakanlah saya harus mengejek suatu kelas Foo<T>yang harus saya sampaikan ke metode yang mengharapkan a Foo<Bar>. Saya dapat melakukan hal berikut dengan cukup mudah:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());

Dengan asumsi getValue()mengembalikan tipe generik T. Tapi itu akan memiliki anak kucing ketika saya nanti meneruskannya ke metode yang diharapkan Foo<Bar>. Apakah casting satu-satunya cara untuk melakukan ini?

Jawaban:


280

Saya pikir Anda perlu melemparkannya, tetapi seharusnya tidak terlalu buruk:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());

34
Ya tapi Anda masih memiliki peringatan. Apakah itu mungkin untuk menghindari peringatan?
odwl

12
@SuppressWarnings ("tidak dicentang")
qualidafial

18
Saya pikir ini sepenuhnya dapat diterima karena kita berbicara tentang objek tiruan dalam tes unit.
Magnilex

1
@demaniak Tidak berhasil sama sekali. Pencocokan argumen tidak dapat digunakan dalam konteks itu.
Krzysztof Krasoń

1
@demaniak Itu akan dikompilasi dengan baik, tetapi ketika menjalankan tes itu akan membuang InvalidUseOfMatchersException (yang merupakan RuntimeException)
Superole

277

Salah satu cara lain untuk mengatasi ini adalah dengan menggunakan @Mockanotasi. Tidak berfungsi dalam semua kasus, tetapi terlihat jauh lebih seksi :)

Ini sebuah contoh:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}

The MockitoJUnitRunnermenginisialisasi bidang dijelaskan dengan @Mock.


3
ini ditinggalkan dalam 1.9.5. :( Sepertinya jauh lebih bersih bagi saya.
Kode Novisiat

12
@CodeNovitiate Saya tidak dapat menemukan anotasi penghentian di MockitoJUnitRunner dan Mock di 1.9.5. Jadi, apa yang sudah usang? (Ya, org.mockito.MockitoAnnotations.Mock sudah tidak digunakan lagi, tetapi Anda harus menggunakan org.mockito.Mock sebagai gantinya)
neu242

12
Bagus, ini bekerja dengan baik untuk saya. Ini bukan hanya "lebih seksi", itu menghindari peringatan tanpa menggunakan SuppressWarnings. Peringatan ada karena suatu alasan, lebih baik untuk tidak terbiasa menekannya. Terima kasih!
Nicole

4
Ada satu hal yang saya tidak suka menggunakan @Mockdaripada mock(): bidang masih nol selama waktu konstruksi, jadi saya tidak bisa memasukkan dependensi pada waktu itu dan tidak bisa membuat bidang menjadi final. Yang pertama dapat dipecahkan dengan @Beforemetode yang tentu saja tidak berotot.
Rüdiger Schulz

3
Untuk inisiasi cukup hubungi MockitoAnnotations.initMocks (ini);
borjab

42

Anda selalu dapat membuat kelas menengah / antarmuka yang akan memenuhi tipe generik yang ingin Anda tentukan. Misalnya, jika Foo adalah antarmuka, Anda bisa membuat antarmuka berikut di kelas pengujian Anda.

private interface FooBar extends Foo<Bar>
{
}

Dalam situasi di mana Foo adalah kelas non-final , Anda bisa memperpanjang kelas dengan kode berikut dan melakukan hal yang sama:

public class FooBar extends Foo<Bar>
{
}

Kemudian Anda dapat menggunakan salah satu dari contoh di atas dengan kode berikut:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());

4
Asalkan Fooantarmuka atau kelas non-final, ini tampaknya menjadi solusi yang cukup elegan. Terima kasih.
Tim Clemons

Saya memperbarui jawaban untuk menyertakan contoh untuk kelas non-final juga. Idealnya Anda akan mengkode terhadap antarmuka, tetapi itu tidak selalu terjadi. Tangkapan yang bagus!
dsingleton

16

Buat metode uji utilitas . Berguna khusus jika Anda membutuhkannya lebih dari satu kali.

@Test
public void testMyTest() {
    // ...
    Foo<Bar> mockFooBar = mockFoo();
    when(mockFooBar.getValue).thenReturn(new Bar());

    Foo<Baz> mockFooBaz = mockFoo();
    when(mockFooBaz.getValue).thenReturn(new Baz());

    Foo<Qux> mockFooQux = mockFoo();
    when(mockFooQux.getValue).thenReturn(new Qux());
    // ...
}

@SuppressWarnings("unchecked") // still needed :( but just once :)
private <T> Foo<T> mockFoo() {
    return mock(Foo.class);
}

Dapat memperluas jawaban Anda untuk membuat metode utilitas umum yang lulus di kelas yang ingin Anda tiru.
William Dutton

1
@ WilliamDutton static <T> T genericMock(Class<? super T> classToMock) { return (T)mock(classToMock); }bahkan tidak perlu penindasan tunggal :) Tapi hati-hati, Integer num = genericMock(Number.class)kompilasi, tetapi melempar ClassCastException. Ini hanya berguna untuk kasus yang paling umum G<P> mock = mock(G.class).
TWiStErRob

6

Saya setuju bahwa seseorang tidak seharusnya menekan peringatan di kelas atau metode karena orang dapat mengabaikan peringatan lainnya, yang secara tidak sengaja ditekan. Tapi IMHO itu benar-benar masuk akal untuk menekan peringatan yang hanya mempengaruhi satu baris kode.

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);

3

Ini adalah kasus yang menarik: metode menerima pengumpulan generik dan mengembalikan koleksi generik dari tipe dasar yang sama. Sebagai contoh:

Collection<? extends Assertion> map(Collection<? extends Assertion> assertions);

Metode ini dapat diejek dengan kombinasi pencocokan Mockito anyCollectionOf dan Jawabannya.

when(mockedObject.map(anyCollectionOf(Assertion.class))).thenAnswer(
     new Answer<Collection<Assertion>>() {
         @Override
         public Collection<Assertion> answer(InvocationOnMock invocation) throws Throwable {
             return new ArrayList<Assertion>();
         }
     });
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.