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);
Andreas Köberle
sumber
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()));
    }
}
crunchdog
sumber
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);
Paŭlo Ebermann
sumber
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.
rogerdpack
sumber
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"));
kkmike999
sumber
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.

Tuan
sumber
2

Untuk versi junit sebelumnya, Anda bisa melakukannya

Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
quzhi65222714
sumber
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();
Timofey Orischenko
sumber
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).

Jezor
sumber