Mockito: Cố gắng theo dõi phương pháp đang gọi phương thức ban đầu


350

Tôi đang sử dụng Mockito 1.9.0. Tôi muốn mô phỏng hành vi cho một phương thức của một lớp trong bài kiểm tra JUnit, vì vậy tôi có

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

Vấn đề là, trong dòng thứ hai, myClassSpy.method1()thực sự được gọi, dẫn đến một ngoại lệ. Lý do duy nhất tôi sử dụng giả là để sau này, bất cứ khi nào myClassSpy.method1()được gọi, phương thức thực sẽ không được gọi và myResultsđối tượng sẽ được trả về.

MyClasslà một giao diện và myInstancelà một thực thi của điều đó, nếu điều đó quan trọng.

Tôi cần làm gì để điều chỉnh hành vi gián điệp này?


Câu trả lời:


609

Hãy để tôi trích dẫn các tài liệu chính thức :

Gotcha quan trọng về gián điệp các đối tượng thực sự!

Đôi khi không thể sử dụng khi (Object) cho các gián điệp khai thác. Thí dụ:

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

Trong trường hợp của bạn, nó đi một cái gì đó như:

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

27
Điều gì xảy ra nếu tôi sử dụng phương pháp này và phương thức ban đầu của tôi VẪN được gọi? Có thể có vấn đề với các thông số tôi vượt qua? Dưới đây là toàn bộ bài kiểm tra: phương pháp pastebin.com/ZieY790P send đang được gọi
Evgeni Petrov

26
@EvgeniPetrov nếu phương thức ban đầu của bạn vẫn được gọi thì có lẽ vì phương thức ban đầu của bạn là cuối cùng. Mockito không chế nhạo các phương thức cuối cùng và không thể cảnh báo bạn về việc chế nhạo các phương thức cuối cùng.
MarcG

1
điều này cũng có thể cho doThrow ()?
Yêu tinh

1
vâng, thật không may, các phương thức tĩnh là không thể thay đổi và "không thể gián điệp". Những gì tôi làm để đối phó với các phương thức tĩnh là bọc một phương thức xung quanh lệnh gọi tĩnh và sử dụng một doNoth hoặc doReturn trên phương thức đó. Với các đối tượng singletons hoặc scala, tôi chuyển phần logic của một lớp trừu tượng và điều đó cho tôi khả năng có một lớp kiểm tra thay thế ngụ ý về đối tượng mà tôi có thể tạo ra một gián điệp.
Andrew Norman

24
Và nếu phương thức tĩnh KHÔNG cuối cùng và KHÔNG vẫn được gọi thì sao?
X-HuMan

27

Trường hợp của tôi khác với câu trả lời được chấp nhận. Tôi đã cố gắng giả định một phương thức riêng tư gói cho một ví dụ không sống trong gói đó

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

và các lớp kiểm tra

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(){}
}

Việc biên dịch là chính xác, nhưng khi nó cố gắng thiết lập thử nghiệm, nó sẽ gọi phương thức thực thay thế.

Tuyên bố phương pháp được bảo vệ hoặc công khai khắc phục sự cố, đó không phải là một giải pháp sạch.


2
Tôi gặp phải một vấn đề tương tự, nhưng phương pháp thử nghiệm và gói riêng tư nằm trong cùng một gói. Tôi nghĩ có lẽ Mockito có vấn đề với các phương thức gói riêng nói chung.
Dave

22

Trong trường hợp của tôi, sử dụng Mockito 2.0, tôi đã phải thay đổi tất cả các any()tham số để nullable()thực hiện cuộc gọi thực sự.


2
Đừng để câu trả lời hàng đầu được bình chọn là 321 khiến bạn thất vọng, điều này đã giải quyết vấn đề của tôi :) Tôi đã vật lộn với điều này trong một vài giờ!
Chris K Tàu

3
Đây là câu trả lời cho tôi. Để làm cho nó dễ dàng hơn cho những người theo sau khi chế nhạo phương thức của bạn, cú pháp là: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder

Với Mockito 2.23.4 tôi có thể xác nhận điều này là không cần thiết, nó hoạt động tốt với anyeqkhớp.
vmaldosan

2
Đã thử ba cách tiếp cận khác nhau trên phiên bản 2.23.4 lib: any (), eq () và nullable (). Chỉ sau này làm việc
ryzhman

Xin chào, giải pháp của bạn thực sự tốt đẹp và làm việc cho tôi là tốt. Cảm ơn
Dhiren Solanki

16

Câu trả lời của Tomasz Nurkiewicz dường như không nói lên toàn bộ câu chuyện!

Phiên bản NB Mockito: 1.10.19.

Tôi rất là một Mockito newb, vì vậy không thể giải thích hành vi sau: nếu có một chuyên gia ngoài kia có thể cải thiện câu trả lời này, xin vui lòng.

Phương pháp trong câu hỏi ở đây getContentStringValue, là KHÔNG finalKHÔNG static .

Dòng này gọi phương thức ban đầu getContentStringValue:

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

Dòng này không gọi phương thức ban đầu getContentStringValue:

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

Vì những lý do mà tôi không thể trả lời, sử dụng isA()nguyên nhân hành vi "không gọi phương thức" dự định (?) Sẽ doReturnthất bại.

Hãy xem các chữ ký phương thức liên quan ở đây: cả hai đều là staticphương pháp Matchers. Cả hai đều được Javadoc nói sẽ quay trở lại null, điều này hơi khó khăn để tự mình quay đầu lại. Có lẽ Classđối tượng được truyền khi tham số được kiểm tra nhưng kết quả không bao giờ được tính toán hoặc loại bỏ. Cho rằng nullcó thể đại diện cho bất kỳ lớp nào và bạn đang hy vọng phương thức giả định không được gọi, không thể là chữ ký của isA( ... )any( ... )chỉ trả về nullchứ không phải là một tham số chung * <T>?

Dù sao:

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

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

Tài liệu API không cung cấp bất kỳ manh mối nào về việc này. Dường như cũng nói rằng nhu cầu đối với hành vi "không gọi phương thức" như vậy là "rất hiếm". Cá nhân tôi sử dụng kỹ thuật này mọi lúc : thông thường tôi thấy rằng việc chế giễu bao gồm một vài dòng "dựng cảnh" ... tiếp theo là gọi một phương thức sau đó "diễn ra" cảnh trong bối cảnh giả mà bạn đã dàn dựng .. và trong khi bạn đang thiết lập cảnh quan và đạo cụ, điều cuối cùng bạn muốn là các diễn viên bước vào sân khấu bên trái và bắt đầu diễn xuất trái tim của họ ...

Nhưng đây là cách vượt quá mức lương của tôi ... Tôi mời những lời giải thích từ bất kỳ linh mục cao cấp nào của Mockito ...

* là "tham số chung" đúng thuật ngữ?


Tôi không biết điều này có làm tăng thêm sự rõ ràng hay làm rối thêm vấn đề hay không, nhưng sự khác biệt giữa isA () và any () là isA thực sự kiểm tra kiểu, trong khi bất kỳ họ () phương thức nào được tạo ra chỉ để tránh truyền kiểu tranh luận.
Kevin Welker

@KevinWelker Cảm ơn. Và thực sự các tên phương thức không thiếu một chất lượng tự giải thích nhất định. Tuy nhiên, tôi làm, và nhẹ nhàng, có vấn đề với các nhà thiết kế thiên tài Mockito vì đã không ghi chép đầy đủ. Không còn nghi ngờ gì nữa, tôi cần đọc một cuốn sách khác về Mockito. PS thực sự dường như có rất ít tài nguyên để dạy "Mockito trung gian"!
mike gặm nhấm

1
Lịch sử là các phương thức anyXX được tạo ra đầu tiên như là một cách để chỉ đối phó với kiểu chữ. Sau đó, khi được đề xuất họ thêm kiểm tra đối số, họ không muốn phá vỡ người dùng API hiện có, vì vậy họ đã tạo ra họ isA (). Biết rằng bất kỳ phương thức () nào cũng nên thực hiện kiểm tra kiểu, họ đã trì hoãn thay đổi chúng cho đến khi chúng đưa ra các thay đổi vi phạm khác trong đại tu Mockito 2.X (mà tôi chưa thử). Trong 2.x +, phương thức anyX () là bí danh cho phương thức isA ().
Kevin Welker

Cảm ơn bạn. Đây là một câu trả lời then chốt cho những người trong chúng ta thực hiện một số cập nhật thư viện cùng một lúc, bởi vì mã được sử dụng để chạy đột ngột và âm thầm thất bại.
Dex Stakker

5

Một tình huống có thể xảy ra hơn có thể gây ra vấn đề với các điệp viên là khi bạn đang thử nghiệm đậu mùa xuân (với khung kiểm tra mùa xuân) hoặc một số khung khác đang làm giả các đối tượng của bạn trong quá trình thử nghiệm .

Thí dụ

@Autowired
private MonitoringDocumentsRepository repository

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

Trong đoạn mã trên, cả Spring và Mockito sẽ cố gắng ủy quyền cho đối tượng MonitorDocumentRep repository của bạn, nhưng Spring sẽ là đầu tiên, điều này sẽ gây ra cuộc gọi thực sự của phương thức findMonitoringDocument. Nếu chúng ta gỡ lỗi mã của mình ngay sau khi đặt một gián điệp vào đối tượng kho lưu trữ, nó sẽ trông như thế này bên trong trình gỡ lỗi:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean đến giải cứu

Nếu thay vì @Autowiredchú thích, chúng tôi sử dụng @SpyBeanchú thích, chúng tôi sẽ giải quyết vấn đề ở trên, chú thích SpyBean cũng sẽ tiêm đối tượng kho lưu trữ nhưng trước tiên nó sẽ được Mockito ủy quyền và sẽ giống như thế này bên trong trình gỡ lỗi

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

và đây là mã:

@SpyBean
private MonitoringDocumentsRepository repository

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

1

Tôi đã tìm thấy một lý do khác để gián điệp gọi phương thức ban đầu.

Ai đó đã có ý tưởng để chế nhạo một finallớp học, và tìm thấy về MockMaker:

Vì cơ chế này hoạt động khác với cơ chế hiện tại của chúng tôi và cơ chế này có những hạn chế khác nhau và vì chúng tôi muốn thu thập kinh nghiệm và phản hồi của người dùng, tính năng này phải được kích hoạt rõ ràng để có sẵn; nó có thể được thực hiện thông qua cơ chế mở rộng mockito bằng cách tạo tệp src/test/resources/mockito-extensions/org.mockito.plugins.MockMakerchứa một dòng duy nhất:mock-maker-inline

Nguồn: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final- classesmethods

Sau khi tôi hợp nhất và mang tập tin đó vào máy, các thử nghiệm của tôi đã thất bại.

Tôi chỉ cần xóa dòng (hoặc tệp), và spy()làm việc.


đây là lý do trong trường hợp của tôi, tôi đã cố gắng chế giễu một phương thức cuối cùng nhưng nó vẫn gọi phương thức thực sự mà không có thông báo lỗi rõ ràng gây nhầm lẫn.
Bashar Ali Labadi

1

Bit đến bữa tiệc muộn nhưng các giải pháp trên không hiệu quả với tôi, vì vậy chia sẻ 0,02 đô la của tôi

Phiên bản Mokcito: 1.10.19

MyClass.java

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

Test.java

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

Sau đây KHÔNG làm việc cho tôi (phương pháp thực tế đã được gọi):

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

Sau khi làm việc:

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

0

Một cách để đảm bảo một phương thức từ một lớp không được gọi là ghi đè phương thức bằng một hình nộm.

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

-1

Trả lời cho người dùng scala: Ngay cả việc đặt doReturnđầu tiên không hoạt động! Xem bài này .


đây không phải là một câu trả lời
Umpa
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.