Mockito: การพยายามสอดแนมวิธีกำลังเรียกใช้วิธีดั้งเดิม


352

ฉันใช้ Mockito 1.9.0 ฉันต้องการจำลองพฤติกรรมสำหรับวิธีการหนึ่งของชั้นเรียนในการทดสอบ JUnit ดังนั้นฉันมี

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

ปัญหาคือในบรรทัดที่สองmyClassSpy.method1()กำลังถูกเรียกใช้จริงส่งผลให้เกิดข้อยกเว้น เหตุผลเดียวที่ฉันใช้ mocks คือเพื่อที่ในภายหลังเมื่อใดก็ตามที่myClassSpy.method1()มีการเรียกวิธีการจริงจะไม่ถูกเรียกและmyResultsวัตถุจะถูกส่งกลับ

MyClassเป็นอินเทอร์เฟซและmyInstanceเป็นการนำไปปฏิบัติหากสิ่งนั้นสำคัญ

ฉันต้องทำอะไรเพื่อแก้ไขพฤติกรรมการสอดแนมนี้


ดูที่นี่: stackoverflow.com/a/29394497/355438
Lu55

คำตอบ:


610

ให้ฉันอ้างอิงเอกสารอย่างเป็นทางการ :

Gotcha สำคัญในการสอดแนมวัตถุจริง!

บางครั้งมันเป็นไปไม่ได้ที่จะใช้เมื่อ (Object) สำหรับการจารชนสายลับ ตัวอย่าง:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

ในกรณีของคุณมันจะเป็นเช่น:

doReturn(resulstIWant).when(myClassSpy).method1();

27
ถ้าฉันใช้วิธีนี้และวิธีการเดิมของฉันยังคงถูกเรียกใช้อยู่ อาจมีปัญหากับพารามิเตอร์ที่ฉันผ่านหรือไม่ นี่คือการทดสอบทั้งหมด: วิธีการpastebin.com/ZieY790P sendกำลังถูกเรียกใช้
Evgeni Petrov

26
@EvgeniPetrov หากวิธีการดั้งเดิมของคุณยังคงถูกเรียกว่าอาจเป็นเพราะวิธีการดั้งเดิมของคุณเป็นที่สิ้นสุด Mockito ไม่ได้จำลองวิธีการขั้นสุดท้ายและไม่สามารถเตือนคุณเกี่ยวกับการจำลองวิธีการขั้นสุดท้ายได้
MarcG

1
เป็นไปได้สำหรับ doThrow ()?
Gobliins

1
ใช่น่าเสียดายที่วิธีการแบบคงที่นั้นไม่สามารถทำการถอดได้และ "ไม่สามารถสอดแนมได้" สิ่งที่ฉันทำเพื่อจัดการกับวิธีการแบบคงที่คือการห่อวิธีรอบโทรคงที่และใช้ doNothing หรือ doReturn ในวิธีการที่ ด้วยวัตถุซิงเกิลหรือสกาล่าฉันย้ายเนื้อของตรรกะไปยังคลาสนามธรรมและนั่นทำให้ฉันมีความสามารถในการมีคลาสการทดสอบทางเลือกโดยนัยของวัตถุที่ฉันสามารถสร้างสายลับได้
Andrew Norman

24
และถ้าหากวิธีการแบบไม่สิ้นสุดและไม่คงที่ยังคงถูกเรียกใช้อยู่
X-HuMan

27

กรณีของฉันแตกต่างจากคำตอบที่ยอมรับ ฉันพยายามจำลองวิธีแพคเกจส่วนตัวสำหรับอินสแตนซ์ที่ไม่ได้อยู่ในแพ็คเกจนั้น

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

และชั้นเรียนทดสอบ

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

การรวบรวมถูกต้อง แต่เมื่อพยายามตั้งค่าการทดสอบจะเรียกใช้วิธีการจริงแทน

การประกาศวิธีที่ได้รับการป้องกันหรือแก้ไขปัญหาสาธารณะไม่ใช่วิธีการที่สะอาด


2
ฉันพบปัญหาที่คล้ายกัน แต่วิธีการทดสอบและแพ็คเกจส่วนตัวอยู่ในแพ็คเกจเดียวกัน ฉันคิดว่าบางที Mockito มีปัญหากับวิธีการแพคเกจส่วนตัวโดยทั่วไป
เดฟ

22

ในกรณีของฉันโดยใช้ Mockito 2.0 ฉันต้องเปลี่ยนany()พารามิเตอร์ทั้งหมดเป็นnullable()เพื่อไม่ให้โทรจริง


2
อย่าปล่อยให้คำตอบที่ได้รับคะแนนโหวต 321 ทำให้คุณผิดหวังนี่แก้ไขปัญหาของฉันได้ :) ฉันกำลังดิ้นรนกับเรื่องนี้สองสามชั่วโมง!
Chris Kessel

3
นี่คือคำตอบสำหรับฉัน เพื่อให้ง่ายยิ่งขึ้นสำหรับผู้ที่ติดตามเมื่อเย้ยหยันวิธีการของคุณไวยากรณ์คือ: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
ไตรเดอ

ด้วย Mockito 2.23.4 ฉันสามารถยืนยันได้ว่ามันไม่จำเป็นมันทำงานได้ดีanyและeqจับคู่
vmaldosan

2
พยายามสามวิธีที่แตกต่างกันในเวอร์ชัน 2.23.4 lib: any (), eq () และ nullable () เฉพาะการทำงานในภายหลังเท่านั้น
ryzhman

สวัสดีวิธีแก้ปัญหาของคุณดีมากและก็ใช้ได้กับฉันเช่นกัน ขอบคุณ
Dhiren Solanki

16

คำตอบของ Tomasz Nurkiewicz ดูเหมือนจะไม่บอกเรื่องราวทั้งหมด!

รุ่น NB Mockito: 1.10.19

ฉันเป็น Mockito newb เป็นอย่างมากดังนั้นจึงไม่สามารถอธิบายพฤติกรรมดังต่อไปนี้: หากมีผู้เชี่ยวชาญที่สามารถปรับปรุงคำตอบนี้ได้โปรดอย่าลังเล

วิธีการในคำถามที่นี่ที่getContentStringValueเป็นไม่ได้ finalและไม่ได้ static

บรรทัดนี้เรียกวิธีการเดิมgetContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

บรรทัดนี้ไม่ได้เรียกวิธีการเดิมgetContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

สำหรับเหตุผลที่ฉันไม่สามารถตอบได้การใช้isA()ทำให้พฤติกรรมที่ตั้งใจ (?) "ไม่เรียกใช้เมธอด" ของที่doReturnจะล้มเหลว

ดู Let 's ที่ลายเซ็นวิธีการที่เกี่ยวข้องกับที่นี่: พวกเขาเป็นทั้งวิธีการstatic Matchersทั้งคู่กล่าวโดย Javadoc ที่จะกลับมาnullซึ่งเป็นเรื่องยากเล็กน้อยที่จะนำพาคุณไปรอบ ๆ สันนิษฐานว่าClassวัตถุที่ส่งผ่านขณะที่พารามิเตอร์ถูกตรวจสอบ แต่ผลลัพธ์ไม่เคยถูกคำนวณหรือทิ้ง ระบุว่าnullสามารถยืนสำหรับการเรียนใด ๆ และที่คุณกำลังหวังสำหรับวิธีการเยาะเย้ยไม่ได้ที่จะเรียกว่าไม่สามารถลงลายมือชื่อผู้isA( ... )และany( ... )เพียงแค่กลับมาnullแทนที่จะเป็นพารามิเตอร์ทั่วไป * <T>?

อย่างไรก็ตาม:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

เอกสาร API ไม่ได้ให้เงื่อนงำใด ๆ เกี่ยวกับเรื่องนี้ นอกจากนี้ยังดูเหมือนว่าต้องการพฤติกรรมดังกล่าว "ไม่เรียกวิธี" คือ "หายากมาก" โดยส่วนตัวแล้วฉันใช้เทคนิคนี้ตลอดเวลา : โดยทั่วไปแล้วฉันพบว่าการเยาะเย้ยนั้นเกี่ยวข้องกับสองสามบรรทัดที่ "กำหนดฉาก" ... ตามด้วยการเรียกวิธีการที่ "เล่น" ฉากในบริบทจำลองซึ่งคุณแสดงไว้ .. และในขณะที่คุณกำลังจัดฉากและอุปกรณ์ประกอบฉากสิ่งสุดท้ายที่คุณต้องการคือให้นักแสดงเข้าสู่เวทีด้านซ้ายและเริ่มแสดงหัวใจของพวกเขาออกไป ...

แต่นี่เป็นวิธีที่เกินระดับการจ่ายเงินของฉัน ... ฉันเชิญคำอธิบายจากมหาปุโรหิต Mockito ที่ผ่านมา ...

* คือ "พารามิเตอร์ทั่วไป" เป็นคำที่ถูกต้องหรือไม่


ฉันไม่รู้ว่าสิ่งนี้เพิ่มความชัดเจนหรือทำให้เกิดความสับสนมากขึ้นหรือไม่ แต่ความแตกต่างระหว่าง isA () และใด ๆ () คือ isA ทำการตรวจสอบประเภทจริง ๆ ในขณะที่วิธีการใด ๆ () สร้างวิธีการง่ายๆ การโต้เถียง.
Kevin Welker

@KevinWelker ขอบคุณ และแน่นอนว่าชื่อวิธีการนั้นไม่ได้ขาดในคุณภาพที่อธิบายได้ด้วยตนเอง ฉันทำ แต่และอย่างอ่อนโยนมีปัญหากับนักออกแบบอัจฉริยะ Mockito สำหรับการบันทึกไม่เพียงพอ ไม่ต้องสงสัยเลยว่าฉันต้องอ่านหนังสืออีกเล่มเกี่ยวกับ Mockito PS จริง ๆ แล้วดูเหมือนจะมีทรัพยากรน้อยมากที่จะสอน "สื่อกลาง Mockito"!
ไมค์หนู

1
ประวัติความเป็นมาของเมธอด anyXX นั้นถูกสร้างขึ้นเป็นวิธีจัดการกับ typecasting เท่านั้น จากนั้นเมื่อมีการแนะนำให้เพิ่มการตรวจสอบอาร์กิวเมนต์พวกเขาไม่ต้องการทำลายผู้ใช้ของ API ที่มีอยู่ดังนั้นพวกเขาจึงสร้างตระกูล isA () เมื่อรู้ว่าวิธีการใด ๆ () ควรทำการตรวจสอบประเภทตลอดพวกเขาล่าช้าในการเปลี่ยนแปลงจนกว่าพวกเขาจะแนะนำการเปลี่ยนแปลงที่ผิดพลาดอื่น ๆ ในการยกเครื่อง Mockito 2.X (ซึ่งฉันยังไม่ได้ลอง) ใน 2.x + เมธอด anyX () เป็นสมนามสำหรับเมธอด isA ()
Kevin Welker

ขอบคุณ. นี่เป็นคำตอบที่สำคัญสำหรับพวกเราที่ทำการอัพเดทหลาย ๆ ไลบรารีในเวลาเดียวกันเพราะโค้ดที่ใช้ในการทำงานนั้นเกิดขึ้นทันทีและล้มเหลวอย่างเงียบ ๆ
Dex Stakker

6

หนึ่งในสถานการณ์ที่เป็นไปได้มากขึ้นซึ่งอาจก่อให้เกิดปัญหาเกี่ยวกับสายลับคือเมื่อคุณกำลังทดสอบถั่วฤดูใบไม้ผลิ (มีกรอบทดสอบฤดูใบไม้ผลิ) หรืออื่น ๆกรอบที่ proxing วัตถุของคุณในระหว่างการทดสอบ

ตัวอย่าง

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

ในโค้ดด้านบนทั้ง Spring และ Mockito จะพยายามพร็อกซีออบเจ็กต์ MonitoringDocumentsRepository ของคุณ แต่ Spring จะเป็นอันดับแรกซึ่งจะทำให้เกิดการเรียกใช้เมธอด findMonitoringDocuments จริง ถ้าเราทำการดีบั๊กโค้ดของเราหลังจากใส่สอดแนมบนวัตถุที่เก็บมันจะมีลักษณะเช่นนี้ภายในดีบักเกอร์:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpBeBean เพื่อช่วยเหลือ

ถ้าแทนที่จะใช้@Autowiredคำอธิบายประกอบเราใช้@SpyBeanคำอธิบายประกอบเราจะแก้ปัญหาข้างต้นคำอธิบายประกอบ SpyBean จะฉีดวัตถุที่เก็บ แต่มันจะถูกพร็อกซีแรกโดย Mockito และจะมีลักษณะเช่นนี้ภายในดีบักเกอร์

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

และนี่คือรหัส:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

1

ฉันได้พบอีกเหตุผลหนึ่งที่สายลับเรียกวิธีดั้งเดิม

ใครบางคนมีความคิดที่จะล้อเลียนfinalชั้นเรียนและพบเกี่ยวกับMockMaker:

เนื่องจากสิ่งนี้ทำงานแตกต่างจากกลไกปัจจุบันของเราและสิ่งนี้มีข้อ จำกัด ที่แตกต่างกันและเมื่อเราต้องการรวบรวมประสบการณ์และความคิดเห็นจากผู้ใช้คุณลักษณะนี้จึงต้องเปิดใช้งานอย่างชัดเจนเพื่อให้พร้อมใช้งาน มันสามารถทำได้ผ่านกลไกการขยาย mockito โดยการสร้างไฟล์src/test/resources/mockito-extensions/org.mockito.plugins.MockMakerที่มีบรรทัดเดียว:mock-maker-inline

ที่มา: https://github.com/mockito/mockito/wiki/What

หลังจากฉันรวมและนำไฟล์นั้นไปที่เครื่องการทดสอบของฉันล้มเหลว

ฉันต้องลบบรรทัด (หรือไฟล์) ออกและใช้spy()งานได้


นี่คือเหตุผลในกรณีของฉันฉันพยายามที่จะเยาะเย้ยวิธีการขั้นสุดท้าย แต่มันก็เรียกจริงว่าไม่มีข้อผิดพลาดที่ชัดเจนซึ่งทำให้สับสน
Bashar Ali Labadi

1

ปาร์ตี้ช้าไปหน่อย แต่วิธีแก้ปัญหาด้านบนไม่ได้ผลสำหรับฉันดังนั้นแชร์ 0.02 $ ของฉัน

รุ่น Mokcito: 1.10.19

MyClass.java

private int handleAction(List<String> argList, String action)

Test.java

MyClass spy = PowerMockito.spy(new MyClass());

การติดตามไม่ได้ผลสำหรับฉัน (วิธีการจริงถูกเรียก):

1

doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());

2

doReturn(0).when(spy , "handleAction", any(), anyString());

3

doReturn(0).when(spy , "handleAction", null, null);

กำลังติดตามการทำงาน:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());

0

วิธีหนึ่งในการตรวจสอบให้แน่ใจว่าไม่มีการเรียกใช้เมธอดจากคลาสคือการแทนที่เมธอดด้วยดัมมี่

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });

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