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?

Tim Clemons
sumber

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());
John Paulett
sumber
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.

Marek Kirejczyk
sumber
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());
dsingleton
sumber
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);
}
acdcjunior
sumber
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);
Tobias Uhmann
sumber
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>();
         }
     });
qza
sumber