Cara menangkap daftar tipe tertentu dengan mockito


301

Apakah ada cara untuk menangkap daftar tipe tertentu menggunakan mockitos ArgumentCaptore. Ini tidak berfungsi:

ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);

8
Saya menemukan bahwa itu adalah ide yang buruk untuk menggunakan implementasi daftar konkret di sini ( ArrayList). Anda selalu dapat menggunakan Listantarmuka, dan jika Anda ingin merepresentasikan fakta, bahwa itu kovarian, maka Anda dapat menggunakan extends:ArgumentCaptor<? extends List<SomeType>>
tenshi

Jawaban:


533

Masalah generik bersarang dapat dihindari dengan penjelasan @Captor :

public class Test{

    @Mock
    private Service service;

    @Captor
    private ArgumentCaptor<ArrayList<SomeType>> captor;

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

    @Test 
    public void shouldDoStuffWithListValues() {
        //...
        verify(service).doStuff(captor.capture()));
    }
}

70
Saya lebih suka menggunakan MockitoAnnotations.initMocks(this)dalam @Beforemetode daripada menggunakan pelari yang tidak termasuk kemampuan untuk menggunakan pelari lain. Namun, +1, terima kasih telah menunjukkan anotasi.
John B

4
Tidak yakin contoh ini selesai. Saya mendapatkan ... Kesalahan: (240, 40) java: penculik variabel mungkin belum diinisialisasi saya suka jawaban tenshi di bawah ini
Michael Dausmann

1
Saya mengalami masalah yang sama, dan menemukan posting blog ini yang sedikit membantu saya: blog.jdriven.com/2012/10/… . Ini termasuk langkah untuk menggunakan MockitoAnnotations.initMocks setelah Anda meletakkan anotasi di kelas Anda. Satu hal yang saya perhatikan adalah Anda tidak dapat memilikinya dalam variabel lokal.
SlopeOak

1
@ chamzz.dot ArgumentCaptor <ArrayList <SomeType>> penculiknya; sudah menangkap larik "SomeType" <- itu tipe tertentu, bukan?
Miguel R. Santaella

1
Saya biasanya lebih memilih List daripada ArrayList dalam deklarasi Captor: ArgumentCaptor <List <SomeType>> captor;
Miguel R. Santaella

146

Ya, ini masalah umum generik, bukan mockito-spesifik.

Tidak ada objek kelas untuk ArrayList<SomeType>, dan karenanya Anda tidak bisa mengetik-aman meneruskan objek seperti itu ke metode yang membutuhkan a Class<ArrayList<SomeType>>.

Anda bisa melemparkan objek ke tipe yang tepat:

Class<ArrayList<SomeType>> listClass =
              (Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);

Ini akan memberikan beberapa peringatan tentang gips yang tidak aman, dan tentu saja ArgumentCaptor Anda tidak dapat benar-benar membedakan antara ArrayList<SomeType>dan ArrayList<AnotherType>tanpa mungkin memeriksa elemen.

(Seperti yang disebutkan dalam jawaban lain, sementara ini adalah masalah umum umum, ada solusi spesifik Mockito untuk masalah keamanan jenis dengan @Captoranotasi. Itu masih tidak dapat membedakan antara ArrayList<SomeType>dan ArrayList<OtherType>.)

Edit:

Lihatlah juga komentar tenshi . Anda dapat mengubah kode asli dari Paŭlo Ebermann ke ini (lebih sederhana)

final ArgumentCaptor<List<SomeType>> listCaptor
        = ArgumentCaptor.forClass((Class) List.class);

49
Contoh yang Anda tunjukkan dapat disederhanakan, berdasarkan pada fakta bahwa java membuat inferensi tipe untuk panggilan metode statis:ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
tenshi

4
Untuk menonaktifkan penggunaan peringatan operasi yang tidak dicentang atau tidak aman , gunakan @SuppressWarnings("unchecked")anotasi di atas garis definisi penculik argumen. Juga, casting to Classredundant.
mrts

1
Pengecoran untuk Classtidak berlebihan dalam pengujian saya.
Wim Deblauwe

16

Jika Anda tidak takut dengan semantik gaya java lama (non type safe generic), ini juga berfungsi dan cukup sederhana:

ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(subject.method(argument.capture()); // run your code
List<SomeType> list = argument.getValue(); // first captured List, etc.

2
Anda dapat menambahkan @SuppressWarnings ("rawtypes") sebelum deklarasi untuk menonaktifkan peringatan.
pkalinow

9
List<String> mockedList = mock(List.class);

List<String> l = new ArrayList();
l.add("someElement");

mockedList.addAll(l);

ArgumentCaptor<List> argumentCaptor = ArgumentCaptor.forClass(List.class);

verify(mockedList).addAll(argumentCaptor.capture());

List<String> capturedArgument = argumentCaptor.<List<String>>getValue();

assertThat(capturedArgument, hasItem("someElement"));

4

Berdasarkan komentar @ tenshi dan @ pkalinow (juga pujian untuk @rogerdpack), berikut ini adalah solusi sederhana untuk membuat daftar argumen penculiknya yang juga menonaktifkan peringatan "penggunaan operasi yang tidak dicentang atau tidak aman" :

@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
    ArgumentCaptor.forClass(List.class);

Contoh lengkap di sini dan pembangunan CI lulus yang sesuai dan uji coba di sini .

Tim kami telah menggunakan ini selama beberapa waktu dalam unit test kami dan ini sepertinya solusi paling mudah bagi kami.


2

Untuk versi junit sebelumnya, Anda bisa melakukannya

Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);

1

Saya memiliki masalah yang sama dengan aktivitas pengujian di aplikasi Android saya. Saya menggunakan ActivityInstrumentationTestCase2dan MockitoAnnotations.initMocks(this);tidak bekerja. Saya memecahkan masalah ini dengan kelas lain dengan bidang masing-masing. Sebagai contoh:

class CaptorHolder {

        @Captor
        ArgumentCaptor<Callback<AuthResponse>> captor;

        public CaptorHolder() {
            MockitoAnnotations.initMocks(this);
        }
    }

Kemudian, dalam metode uji aktivitas:

HubstaffService hubstaffService = mock(HubstaffService.class);
fragment.setHubstaffService(hubstaffService);

CaptorHolder captorHolder = new CaptorHolder();
ArgumentCaptor<Callback<AuthResponse>> captor = captorHolder.captor;

onView(withId(R.id.signInBtn))
        .perform(click());

verify(hubstaffService).authorize(anyString(), anyString(), captor.capture());
Callback<AuthResponse> callback = captor.getValue();

0

Ada masalah terbuka di GitHub Mockito tentang masalah ini.

Saya telah menemukan solusi sederhana yang tidak memaksa Anda untuk menggunakan anotasi dalam tes Anda:

import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;

public final class MockitoCaptorExtensions {

    public static <T> ArgumentCaptor<T> captorFor(final CaptorTypeReference<T> argumentTypeReference) {
        return new CaptorContainer<T>().captor;
    }

    public static <T> ArgumentCaptor<T> captorFor(final Class<T> argumentClass) {
        return ArgumentCaptor.forClass(argumentClass);
    }

    public interface CaptorTypeReference<T> {

        static <T> CaptorTypeReference<T> genericType() {
            return new CaptorTypeReference<T>() {
            };
        }

        default T nullOfGenericType() {
            return null;
        }

    }

    private static final class CaptorContainer<T> {

        @Captor
        private ArgumentCaptor<T> captor;

        private CaptorContainer() {
            MockitoAnnotations.initMocks(this);
        }

    }

}

Apa yang terjadi di sini adalah bahwa kita membuat kelas baru dengan para @Captorpenjelasan dan menyuntikkan penculiknya ke dalamnya. Kemudian kita cukup mengekstrak penculiknya dan mengembalikannya dari metode statis kami.

Dalam tes Anda, Anda dapat menggunakannya seperti ini:

ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());

Atau dengan sintaks yang menyerupai Jackson TypeReference:

ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
    new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
    }
);

Ini berfungsi, karena Mockito sebenarnya tidak memerlukan informasi jenis apa pun (tidak seperti serializers, misalnya).

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.