วิธีการจับภาพรายการประเภทเฉพาะด้วย mockito


301

มีวิธีในการจับภาพรายการประเภทเฉพาะโดยใช้ mockitos ArgumentCaptore สิ่งนี้ใช้ไม่ได้:

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

8
ฉันพบว่ามันเป็นความคิดที่น่ากลัวที่จะใช้การดำเนินการรายการที่เป็นรูปธรรมที่นี่ ( ArrayList) คุณสามารถใช้Listส่วนต่อประสานได้ตลอดเวลาและหากคุณต้องการให้เป็นตัวแทนของความจริงมันเป็น covariant คุณสามารถใช้extends:ArgumentCaptor<? extends List<SomeType>>
tenshi

คำตอบ:


533

ข้อมูลทั่วไปเกี่ยวกับปัญหาซ้อนสามารถหลีกเลี่ยงได้ด้วยคำอธิบายประกอบ @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
ฉันชอบที่จะใช้MockitoAnnotations.initMocks(this)ใน@Beforeวิธีการแทนที่จะใช้นักวิ่งที่ไม่รวมความสามารถในการใช้นักวิ่งคนอื่น อย่างไรก็ตาม +1 ขอบคุณสำหรับการชี้ให้เห็นคำอธิบายประกอบ
John B

4
ไม่แน่ใจว่าตัวอย่างนี้เสร็จสมบูรณ์แล้ว ฉันได้รับ ... ข้อผิดพลาด: (240, 40) java: ตัวแปร captor อาจไม่ได้รับการเริ่มต้นฉันชอบคำตอบของ tenshi ด้านล่าง
Michael Dausmann

1
ฉันวิ่งเข้าไปในปัญหาเดียวกันและพบการโพสต์บล็อกนี้ที่ช่วยให้ฉันบิต: blog.jdriven.com/2012/10/... มันมีขั้นตอนในการใช้ MockitoAnnotations.initMocks หลังจากที่คุณใส่คำอธิบายประกอบในชั้นเรียนของคุณ สิ่งหนึ่งที่ฉันสังเกตเห็นคือคุณไม่สามารถมีได้ในตัวแปรท้องถิ่น
SlopeOak

1
@ chamzz.dot ArgumentCaptor <ArrayList <SomeType>> captor; กำลังจับภาพอาร์เรย์ของ "SomeType" <- ซึ่งเป็นประเภทเฉพาะใช่ไหม
Miguel R. Santaella

1
ฉันมักจะชอบรายการแทนที่จะเป็น ArrayList ในการประกาศแคปเตอร์: ArgumentCaptor <List <SomeType>> captor;
Miguel R. Santaella

146

ใช่นี่เป็นปัญหาทั่วไปเรื่องทั่วไปไม่ใช่เฉพาะม็อกคิโตะ

ไม่มีวัตถุชั้นเรียนสำหรับเป็นและทำให้คุณไม่สามารถพิมพ์-อย่างปลอดภัยผ่านวัตถุดังกล่าวเป็นวิธีการที่ต้องใช้ArrayList<SomeType>Class<ArrayList<SomeType>>

คุณสามารถโยนวัตถุชนิดที่เหมาะสม:

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

สิ่งนี้จะให้คำเตือนบางอย่างเกี่ยวกับการปลดเปลื้องที่ไม่ปลอดภัยและแน่นอนว่า ArgumentCaptor ของคุณจะไม่สามารถแยกความแตกต่างระหว่างArrayList<SomeType>และArrayList<AnotherType>โดยไม่ต้องตรวจสอบองค์ประกอบ

(ดังที่กล่าวไว้ในคำตอบอื่น ๆ ในขณะที่ปัญหานี้เป็นปัญหาทั่วไปข้อมูลทั่วไปมีวิธีแก้ปัญหาเฉพาะด้าน Mockito สำหรับปัญหาความปลอดภัยประเภทด้วยการเพิ่มความ@Captorคิดเห็น แต่ยังไม่สามารถแยกความแตกต่างระหว่างArrayList<SomeType>และArrayList<OtherType>)

แก้ไข:

ลองดูที่ความคิดเห็นของtenshi คุณสามารถเปลี่ยนรหัสต้นฉบับจากPaŭlo Ebermannเป็นแบบนี้ (ง่ายกว่ามาก)

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

49
ตัวอย่างที่คุณแสดงให้เห็นนั้นสามารถทำให้ง่ายขึ้นโดยขึ้นอยู่กับข้อเท็จจริงที่ว่าจาวาทำการอนุมานประเภทสำหรับการเรียกใช้เมธอดแบบคงที่:ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
10

4
หากต้องการปิดใช้งานคำเตือนการใช้งานที่ไม่ได้ตรวจสอบหรือไม่ปลอดภัยให้ใช้@SuppressWarnings("unchecked")คำอธิบายประกอบด้านบนบรรทัดคำจำกัดความตัวระบุอาร์กิวเมนต์ นอกจากนี้การหล่อเพื่อทำClassซ้ำซ้อน
mrts

1
การคัดเลือกนักแสดงClassไม่ได้ซ้ำซ้อนในการทดสอบของฉัน
Wim Deblauwe

16

หากคุณไม่กลัวความหมายทั่วไปของ java-style (ไม่ใช่ประเภททั่วไปที่ปลอดภัย) สิ่งนี้ก็ใช้งานได้และค่อนข้างง่ายเช่นกัน:

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
คุณสามารถเพิ่ม @SuppressWarnings ("rawtypes") ก่อนการประกาศเพื่อปิดการเตือน
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

จากความเห็นของ @ tenhi และ @ pkalinow (เช่นความรุ่งโรจน์ถึง @rogerdpack) ต่อไปนี้เป็นวิธีแก้ไขปัญหาง่ายๆสำหรับการสร้างรายการตัวดักจับอาร์กิวเมนต์ที่ปิดใช้งานคำเตือน"ใช้การดำเนินการที่ไม่ จำกัด หรือไม่ปลอดภัย" :

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

ตัวอย่างเต็มรูปแบบที่นี่และสอดคล้องกันผ่านการสร้าง CI และทดสอบการทำงานที่นี่

ทีมของเราใช้สิ่งนี้มาเป็นระยะเวลาหนึ่งในการทดสอบหน่วยของเราและนี่เป็นวิธีที่ตรงไปตรงมาที่สุดสำหรับเรา


2

สำหรับ Junit รุ่นก่อนหน้าคุณสามารถทำได้

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

1

ฉันมีปัญหาเดียวกันกับกิจกรรมการทดสอบในแอพ Android ของฉัน ฉันใช้ActivityInstrumentationTestCase2และMockitoAnnotations.initMocks(this);ไม่ทำงาน ฉันแก้ไขปัญหานี้ด้วยคลาสอื่นที่มีฟิลด์ตามลำดับ ตัวอย่างเช่น:

class CaptorHolder {

        @Captor
        ArgumentCaptor<Callback<AuthResponse>> captor;

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

จากนั้นในวิธีทดสอบกิจกรรม:

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

มีปัญหาแบบเปิดใน GitHub ของ Mockitoเกี่ยวกับปัญหาที่แน่นอนนี้

ฉันพบวิธีแก้ไขง่ายๆที่ไม่ได้บังคับให้คุณใช้คำอธิบายประกอบในการทดสอบของคุณ:

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

    }

}

จะเกิดอะไรขึ้นที่นี่เป็นที่ที่เราสร้างคลาสใหม่กับ@Captorคำอธิบายประกอบและฉีดจับกุมเป็นมัน จากนั้นเราก็แยกแคปเตอร์และส่งคืนจากวิธีสแตติกของเรา

ในการทดสอบของคุณคุณสามารถใช้มันเพื่อ:

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

หรือด้วยไวยากรณ์ที่คล้ายกับแจ็คสันTypeReference:

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

ใช้งานได้เพราะ Mockito ไม่ต้องการข้อมูลประเภทใด ๆ (เช่น serializers)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.