Cách chụp danh sách loại cụ thể với mockito


301

Có cách nào để nắm bắt một danh sách các loại cụ thể bằng mockitos ArgumentCaptore. Điều này không hoạt động:

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

8
Tôi thấy rằng đó là một ý tưởng khủng khiếp để sử dụng thực hiện danh sách cụ thể ở đây ( ArrayList). Bạn luôn có thể sử dụng Listgiao diện và nếu bạn muốn đại diện cho sự thật, đó là sự đồng biến, thì bạn có thể sử dụng extends:ArgumentCaptor<? extends List<SomeType>>
chục giờ

Câu trả lời:


533

Vấn đề chung chung lồng nhau có thể tránh được với chú thích @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
Tôi thích sử dụng MockitoAnnotations.initMocks(this)trong @Beforephương thức hơn là sử dụng một người chạy loại trừ khả năng sử dụng một người chạy khác. Tuy nhiên, +1, cảm ơn vì đã chỉ ra chú thích.
John B

4
Không chắc chắn ví dụ này là hoàn thành. Tôi nhận được ... Lỗi: (240, 40) java: biến captor có thể chưa được khởi tạo Tôi thích câu trả lời của hàng
chục

1
Tôi gặp vấn đề tương tự, và tìm thấy bài đăng trên blog này đã giúp tôi một chút: blog.jdriven.com/2012/10/ mẹo . Nó bao gồm một bước để sử dụng MockitoAnnotations.initMocks sau khi bạn đặt chú thích vào lớp của mình. Một điều tôi nhận thấy là bạn không thể có nó trong một biến cục bộ.
SlopeOak

1
@ chamzz.dot ArgumentCaptor <ArrayList <someType >> captor; đã bắt được một mảng "Một số loại" <- đó là một loại cụ thể, phải không?
Miguel R. Santaella

1
Tôi thường thích Danh sách thay vì ArrayList trong khai báo Captor: ArgumentCaptor <List <someType >> captor;
Miguel R. Santaella

146

Vâng, đây là một vấn đề chung chung, không phải là mockito cụ thể.

Không có đối tượng lớp nào ArrayList<SomeType>, và do đó bạn không thể nhập một cách an toàn một đối tượng như vậy vào một phương thức yêu cầu a Class<ArrayList<SomeType>>.

Bạn có thể chuyển đối tượng sang đúng loại:

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

Điều này sẽ đưa ra một số cảnh báo về các phôi không an toàn và tất nhiên ArgumentCaptor của bạn không thể thực sự phân biệt giữa ArrayList<SomeType>ArrayList<AnotherType>không có thể kiểm tra các yếu tố.

(Như đã đề cập trong câu trả lời khác, trong khi đây là một vấn đề chung chung, có một giải pháp dành riêng cho Mockito cho vấn đề an toàn loại với @Captorchú thích. Nó vẫn không thể phân biệt giữa một ArrayList<SomeType>và một ArrayList<OtherType>.)

Biên tập:

Hãy cũng xem một bình luận của chục . Bạn có thể thay đổi mã gốc từ Paŭlo Ebermann sang mã này (đơn giản hơn nhiều)

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

49
Ví dụ bạn đã trình bày có thể được đơn giản hóa, dựa trên thực tế là java thực hiện suy luận kiểu cho các cuộc gọi phương thức tĩnh:ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
chục giờ

4
Để vô hiệu hóa việc sử dụng cảnh báo hoạt động không được kiểm soát hoặc không an toàn , hãy sử dụng @SuppressWarnings("unchecked")chú thích phía trên dòng định nghĩa trình giới hạn đối số. Ngoài ra, đúc đến Classlà dư thừa.
mrts

1
Việc chọn để Classkhông thừa trong các thử nghiệm của tôi.
Wim Deblauwe

16

Nếu bạn không sợ ngữ nghĩa cũ (không phải loại an toàn chung) kiểu java, thì điều này cũng hoạt động và khá đơn giản:

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
Bạn có thể thêm @SuppressWarnings ("rawtypes") trước khi khai báo để tắt cảnh báo.
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

Dựa trên các nhận xét của @ chụchihi và @ pkalinow (cũng là kudos cho @rogerdpack), sau đây là một giải pháp đơn giản để tạo một trình tạo đối số danh sách cũng vô hiệu hóa cảnh báo "sử dụng các hoạt động không được kiểm soát hoặc không an toàn" :

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

Ví dụ đầy đủ ở đây và xây dựng CI tương ứng và chạy thử ở đây .

Nhóm của chúng tôi đã sử dụng điều này một thời gian trong các thử nghiệm đơn vị của chúng tôi và đây có vẻ là giải pháp đơn giản nhất cho chúng tôi.


2

Đối với phiên bản cũ hơn của Junit, bạn có thể làm

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

1

Tôi gặp vấn đề tương tự với hoạt động thử nghiệm trong ứng dụng Android của mình. Tôi đã sử dụng ActivityInstrumentationTestCase2MockitoAnnotations.initMocks(this);không làm việc. Tôi đã giải quyết vấn đề này với một lớp khác với trường tương ứng. Ví dụ:

class CaptorHolder {

        @Captor
        ArgumentCaptor<Callback<AuthResponse>> captor;

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

Sau đó, trong phương pháp kiểm tra hoạt động:

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

Có một vấn đề mở trong GitHub của Mockito về vấn đề chính xác này.

Tôi đã tìm thấy một cách giải quyết đơn giản không bắt buộc bạn phải sử dụng các chú thích trong các bài kiểm tra của mình:

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);
        }

    }

}

Chuyện gì xảy ra ở đây là chúng ta tạo ra một lớp mới với các @Captorchú thích và tiêm Captor vào nó. Sau đó, chúng tôi chỉ trích xuất captor và trả về từ phương thức tĩnh của chúng tôi.

Trong thử nghiệm của bạn, bạn có thể sử dụng nó như vậy:

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

Hoặc với cú pháp giống với Jackson TypeReference:

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

Nó hoạt động, bởi vì Mockito thực sự không cần bất kỳ loại thông tin nào (chẳng hạn như serial serial chẳng hạn).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.