Sử dụng Mockito để giả định một số phương pháp nhưng không phải phương pháp khác


402

Có cách nào, sử dụng Mockito, để chế nhạo một số phương thức trong một lớp, nhưng không phải là các phương thức khác không?

Ví dụ, trong lớp này (được thừa nhận) Stocktôi muốn mô phỏng các giá trị getPrice()getQuantity()trả về (như được hiển thị trong đoạn kiểm tra bên dưới) nhưng tôi muốn getValue()thực hiện phép nhân như được mã hóa trong Stocklớp

public class Stock {
  private final double price;
  private final int quantity;

  Stock(double price, int quantity) {
    this.price = price;
    this.quantity = quantity;
  }

  public double getPrice() {
    return price;
  }

  public int getQuantity() {
    return quantity;
  }
  public double getValue() {
    return getPrice() * getQuantity();
  }

  @Test
  public void getValueTest() {
    Stock stock = mock(Stock.class);
    when(stock.getPrice()).thenReturn(100.00);
    when(stock.getQuantity()).thenReturn(200);
    double value = stock.getValue();
    // Unfortunately the following assert fails, because the mock Stock getValue() method does not perform the Stock.getValue() calculation code.
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}

4
Tại sao bạn muốn làm điều đó? Bạn nên kiểm tra lớp (trong trường hợp này, không nên chế giễu) hoặc bạn nên chế nhạo nó trong khi kiểm tra một lớp khác (trong trường hợp đó, không có chức năng). Tại sao bạn sẽ làm một phần giả?
Weltraumpirat

3
Ok, đây là một ví dụ nhỏ về điều thực sự. Trong thực tế, tôi đang cố gắng tránh một cuộc gọi đến cơ sở dữ liệu, bằng cách chuyển các giá trị bị chiếm dụng, nhưng tôi muốn xác minh rằng các phương thức khác hoạt động chính xác với các giá trị được đặt ra đó. Có cách nào tốt hơn để làm điều này?
Victor Grazi

5
Chắc chắn: Chuyển các cuộc gọi cơ sở dữ liệu của bạn sang một lớp riêng (logic truy cập cơ sở dữ liệu và truy cập cơ sở dữ liệu không nên ở cùng một lớp; chúng là hai mối quan tâm khác nhau), trích xuất giao diện của nó, sử dụng giao diện đó để kết nối từ lớp logic miền và chỉ giả định giao diện trong quá trình thử nghiệm.
Weltraumpirat

1
Tôi hoàn toàn đồng ý, thật khó để giải thích toàn bộ bức tranh mà không tải lên những đoạn mã ở đây, kể cả thư viện của bên thứ ba.
Victor Grazi

1
Bạn có thể có thể. Nhưng sau đó, đó sẽ không phải là "cách tốt hơn để làm điều đó": Mã cơ sở dữ liệu của bạn là một chi tiết triển khai mà bạn muốn ẩn khỏi phần còn lại của ứng dụng, thậm chí có thể chuyển sang một gói khác. Bạn sẽ không muốn phải biên dịch lại logic tên miền của mình mỗi khi bạn thay đổi một câu lệnh tiếp theo, phải không?
Weltraumpirat

Câu trả lời:


643

Để trực tiếp trả lời câu hỏi của bạn, vâng, bạn có thể chế giễu một số phương pháp mà không chế giễu người khác. Điều này được gọi là giả một phần . Xem tài liệu Mockito về giả một phần để biết thêm thông tin.

Ví dụ của bạn, bạn có thể làm một cái gì đó như sau, trong bài kiểm tra của bạn:

Stock stock = mock(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
when(stock.getValue()).thenCallRealMethod();  // Real implementation

Trong trường hợp đó, mỗi phương thức thực hiện bị chế giễu, trừ khi được chỉ định thenCallRealMethod()trong when(..)mệnh đề.

Ngoài ra còn có một cách khác với gián điệp thay vì giả :

Stock stock = spy(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
// All other method call will use the real implementations

Trong trường hợp đó, tất cả các phương thức thực hiện là phương thức thực, trừ khi bạn đã xác định một hành vi bị chế giễu với when(..).

Có một cạm bẫy quan trọng khi bạn sử dụng when(Object)với gián điệp như trong ví dụ trước. Phương thức thực sẽ được gọi (vì stock.getPrice()được đánh giá trước khi when(..)chạy). Đây có thể là một vấn đề nếu phương thức của bạn chứa logic không nên được gọi. Bạn có thể viết ví dụ trước như thế này:

Stock stock = spy(Stock.class);
doReturn(100.00).when(stock).getPrice();    // Mock implementation
doReturn(200).when(stock).getQuantity();    // Mock implementation
// All other method call will use the real implementations

Một khả năng khác có thể là sử dụng org.mockito.Mockito.CALLS_REAL_METHODS, chẳng hạn như:

Stock MOCK_STOCK = Mockito.mock( Stock.class, CALLS_REAL_METHODS );

Điều này đại biểu các cuộc gọi chưa được thực hiện để thực hiện thực tế.


Tuy nhiên, với ví dụ của bạn, tôi tin rằng nó vẫn sẽ thất bại, kể từ khi thực hiện getValue()dựa trên quantitypricehơn getQuantity()getPrice(), đó là những gì bạn đã chế giễu.

Một khả năng khác là tránh hoàn toàn giả định:

@Test
public void getValueTest() {
    Stock stock = new Stock(100.00, 200);
    double value = stock.getValue();
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}

21
Tôi nghĩ rằng câu trả lời này là sai. Bạn cần SPY một thể hiện của đối tượng, không MOCK lớp.
GaRRaPeTa

2
@GaRRaPeTa Tôi sẽ nói gián điệp và chế giễu đều là những lựa chọn thay thế hợp lý. Thật khó để nói cái nào là tốt nhất cho trường hợp này, vì OP nói rằng đây là một ví dụ đơn giản.
Jon Newmuis

1
Không phải là "Spy" thay vì "Mock" vì cabe chế giễu một phần được cung cấp bởi "Spy" theo cách tốt hơn.
Tarun Sapra

2
Stock stock = spy(Stock.class);Điều này có vẻ sai, spyphương thức dường như chỉ chấp nhận các đối tượng không phải lớp.
Paramvir Singh Karwal

4
+1 để chỉ ra sự khác biệt giữa doReturn(retval).when(spyObj).methodName(args)when(spyObj.methodName(args)).thenReturn(retval)
Captain_Obingly

140

Việc chế nhạo một phần của một lớp cũng được hỗ trợ thông qua Spy trong mockito

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

//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);

//using the spy calls real methods
spy.add("one");
spy.add("two");

//size() method was stubbed - 100 is printed
System.out.println(spy.size());

Kiểm tra 1.10.192.7.22tài liệu để giải thích chi tiết.


37

Theo tài liệu :

Foo mock = mock(Foo.class, CALLS_REAL_METHODS);

// this calls the real implementation of Foo.getSomething()
value = mock.getSomething();

when(mock.getSomething()).thenReturn(fakeValue);

// now fakeValue is returned
value = mock.getSomething();

2
Cảm ơn bạn đã trình bày cách thiết lập một bản giả trong đó việc triển khai thực sự được gọi cho tất cả các phương thức ngoại trừ một số phương pháp tôi cần kiểm soát từ thử nghiệm.
bigh_29

class NaughtyLinkedList extends LinkedList { public int size() { throw new RuntimeException("don't call me");} } @Test public void partialMockNaughtLinkedList(){ List mock = mock(NaughtyLinkedList.class, CALLS_REAL_METHODS); mock.add(new Object()); // this calls the real function when(mock.size()).thenReturn(2); // For whatever reason, this lines throws the RuntimeException. assertEquals(2,mock.size()); }Điều này không hoạt động. Vì lý do gì, khi "khi" được thực thi, nó thực sự thực thi phương thức được cho là bị chế giễu. Mã:
Lance Kind

3
Vấn đề là "khi nào." "Khi nào" sẽ thực sự thực thi điều bạn muốn chế giễu một phần. Để tránh điều này, có một cách khác: doReturn (). Xem doReturn () tại docs.mockito.googlecode.com/hg/1.9.5/org/mockito/iêu
Lance Kind

18

Những gì bạn muốn là org.mockito.Mockito.CALLS_REAL_METHODStheo các tài liệu:

/**
 * Optional <code>Answer</code> to be used with {@link Mockito#mock(Class, Answer)}
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations.
 * <p>
 * This implementation can be helpful when working with legacy code.
 * When this implementation is used, unstubbed methods will delegate to the real implementation.
 * This is a way to create a partial mock object that calls real methods by default.
 * <p>
 * As usual you are going to read <b>the partial mock warning</b>:
 * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects.
 * How does partial mock fit into this paradigm? Well, it just doesn't... 
 * Partial mock usually means that the complexity has been moved to a different method on the same object.
 * In most cases, this is not the way you want to design your application.
 * <p>
 * However, there are rare cases when partial mocks come handy: 
 * dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.)
 * However, I wouldn't use partial mocks for new, test-driven & well-designed code.
 * <p>
 * Example:
 * <pre class="code"><code class="java">
 * Foo mock = mock(Foo.class, CALLS_REAL_METHODS);
 *
 * // this calls the real implementation of Foo.getSomething()
 * value = mock.getSomething();
 *
 * when(mock.getSomething()).thenReturn(fakeValue);
 *
 * // now fakeValue is returned
 * value = mock.getSomething();
 * </code></pre>
 */

Do đó, mã của bạn sẽ giống như:

import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class StockTest {

    public class Stock {
        private final double price;
        private final int quantity;

        Stock(double price, int quantity) {
            this.price = price;
            this.quantity = quantity;
        }

        public double getPrice() {
            return price;
        }

        public int getQuantity() {
            return quantity;
        }

        public double getValue() {
            return getPrice() * getQuantity();
        }
    }

    @Test
    public void getValueTest() {
        Stock stock = mock(Stock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
        when(stock.getPrice()).thenReturn(100.00);
        when(stock.getQuantity()).thenReturn(200);
        double value = stock.getValue();

        assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
    }
}

Cuộc gọi đến Stock stock = mock(Stock.class);các cuộc gọi org.mockito.Mockito.mock(Class<T>)giống như thế này:

 public static <T> T mock(Class<T> classToMock) {
    return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS));
}

Các tài liệu của giá trị RETURNS_DEFAULTSnói:

/**
 * The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
 * Typically it just returns some empty value. 
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations. 
 * <p>
 * This implementation first tries the global configuration. 
 * If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.)
 */

1
Cũng phát hiện ra ... nhưng tôi có thể hỏi tại sao bạn sử dụng withSettings()...như vậy không? Dường như org.mockito.internal.stubbing.answers.CallsRealMethods()(ví dụ) có thể thực hiện công việc ... và javadoc cho lớp này đặc biệt nói rằng nó được sử dụng cho giả một phần ...
loài gặm nhấm

3
Ngoài ra ... sẽ không gặp phải vấn đề mà các câu trả lời khác gặp phải ở đây: tức là thenReturnsẽ thực sự thực thi phương thức (có thể gây ra sự cố, mặc dù không phải trong ví dụ này), và vì vậy doReturncó thích hợp hơn trong trường hợp như vậy không ...?
loài gặm nhấm mike

4

Chế giễu một phần bằng phương pháp gián điệp của Mockito có thể là giải pháp cho vấn đề của bạn, như đã nêu trong các câu trả lời ở trên. Ở một mức độ nào đó, tôi đồng ý rằng, đối với trường hợp sử dụng cụ thể của bạn, có thể phù hợp hơn để chế nhạo việc tra cứu DB. Từ kinh nghiệm của tôi, điều này không phải lúc nào cũng có thể - ít nhất là không phải không có cách giải quyết khác - mà tôi sẽ coi là rất cồng kềnh hoặc ít nhất là mong manh. Lưu ý, chế độ nhạo báng một phần không hoạt động với các phiên bản đồng minh của Mockito. Bạn đã sử dụng ít nhất 1.8.0.

Tôi sẽ chỉ viết một bình luận đơn giản cho câu hỏi ban đầu thay vì đăng câu trả lời này, nhưng StackOverflow không cho phép điều này.

Chỉ một điều nữa: Tôi thực sự không thể hiểu rằng nhiều lần một câu hỏi đang được hỏi ở đây nhận được bình luận với "Tại sao bạn muốn làm điều này" mà ít nhất là cố gắng hiểu vấn đề. Đặc biệt là khi cần phải chế nhạo một phần, thực sự có rất nhiều trường hợp sử dụng mà tôi có thể tưởng tượng nó sẽ hữu ích ở đâu. Đó là lý do tại sao những người từ Mockito cung cấp chức năng đó. Tính năng này tất nhiên không nên quá lạm dụng. Nhưng khi chúng ta nói về các thiết lập trường hợp thử nghiệm mà mặt khác không thể được thiết lập theo một cách rất phức tạp, thì nên sử dụng gián điệp.


2
Tôi cảm thấy câu trả lời này là một phần ý kiến. Hãy xem xét chỉnh sửa.
soundlikeodd

2
Ủng hộ để cổ vũ thành viên mới trong gia đình. Không cần phải có vùng inve này, không có gì thực sự sai về mặt kỹ thuật hoặc ngôn ngữ / âm điệu không chính xác. Hãy tử tế với các thành viên mới. Cảm ơn.
Saurabh Patil
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.