Cách mô phỏng các phương thức void với Mockito


938

Làm thế nào để mô phỏng các phương thức với kiểu trả về void?

Tôi đã triển khai mô hình người quan sát nhưng tôi không thể chế giễu nó bằng Mockito vì tôi không biết làm thế nào.

Và tôi đã cố gắng tìm một ví dụ trên Internet nhưng không thành công.

Lớp học của tôi trông như thế này:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

Hệ thống không được kích hoạt với giả.

Tôi muốn hiển thị trạng thái hệ thống nêu trên. Và đưa ra khẳng định theo họ.


6
Coi chừng các phương thức void trên mocks không làm gì theo mặc định!
Dòng

1
@Line, đó là những gì tôi đang tìm kiếm. Có vẻ hiển nhiên sau khi bạn nói nó. Nhưng nó làm nổi bật một nguyên tắc chế giễu: Bạn chỉ cần chế nhạo các phương thức của các lớp bị chế giễu cho các hiệu ứng của chúng, như giá trị trả về hoặc ngoại lệ. Cảm ơn bạn!
allenjom

Câu trả lời:


1145

Hãy xem các tài liệu API Mockito . Khi tài liệu liên quan đề cập (Point # 12) bạn có thể sử dụng bất kỳ doThrow(), doAnswer(), doNothing(), doReturn()gia đình của các phương pháp từ khuôn khổ Mockito để thử phương pháp vô hiệu.

Ví dụ,

Mockito.doThrow(new Exception()).when(instance).methodName();

hoặc nếu bạn muốn kết hợp nó với hành vi tiếp theo,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

Giả sử rằng bạn đang xem chế độ setter setState(String s)trong thế giới lớp bên dưới là mã sử dụng doAnswerphương thức để giả định setState.

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());

8
@qualidafial: Vâng, tôi nghĩ rằng việc tham số hóa cho Void sẽ tốt hơn vì nó truyền tải tốt hơn rằng tôi không quan tâm đến kiểu trả về. Tôi đã không nhận thức được cấu trúc này, cảm ơn vì đã chỉ ra nó.
sateesh

2
doThrow hiện là # 5 (đối với tôi sử dụng doThrow đã sửa lỗi thông báo "'void' loại không được phép ở đây", cho người theo dõi ...)
rogerdpack

@qualidafial: Tôi nghĩ rằng kiểu trả về của cuộc gọi Trả lời.
mười hai17

1
:( khi thử Mock phiên bản 16.0.1 của RateLimiter.java trong guava doNoth (). Khi (mockLimiterReject) .setRate (100) dẫn đến việc gọi teh setRate của RateLimiter dẫn đến null vì vậy nó không chế nhạo phương thức setRate của tôi :( mà thay vào đó gọi nó là :(
Dean Hiller

2
@DeanHiller thông báo đó setRate()final, và do đó không thể bị chế giễu. Thay vào đó hãy thử create()một ví dụ thực hiện những gì bạn cần. Không cần phải chế giễu RateLimiter.
dimo414

113

Tôi nghĩ rằng tôi đã tìm thấy một câu trả lời đơn giản hơn cho câu hỏi đó, để gọi phương thức thực sự chỉ cho một phương thức (ngay cả khi nó có trả về khoảng trống), bạn có thể làm điều này:

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

Hoặc, bạn có thể gọi phương thức thực cho tất cả các phương thức của lớp đó, thực hiện điều này:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);

13
Đây là câu trả lời thực sự ngay tại đây. Phương thức spy () hoạt động tốt, nhưng thường được dành riêng khi bạn muốn đối tượng thực hiện hầu hết mọi thứ bình thường.
biggusjimmus

1
Điều đó có nghĩa là gì? Bạn đang thực sự gọi các phương thức? Tôi chưa thực sự sử dụng mockito trước đây.
obesechicken13

Vâng, giả sẽ gọi các phương thức thực sự. Nếu bạn sử dụng @Mock, bạn có thể chỉ định tương tự với: @Mock (answer = answer.CALLS_REAL_METHODS) để có được kết quả tương tự.
Ale

70

Thêm vào những gì @sateesh đã nói, khi bạn chỉ muốn chế nhạo một phương thức void để ngăn kiểm tra gọi nó, bạn có thể sử dụng một Spycách này:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

Khi bạn muốn chạy thử nghiệm của mình, hãy đảm bảo bạn gọi phương thức trong thử nghiệm trên spyđối tượng chứ không phải trên worldđối tượng. Ví dụ:

assertEquals(0,spy.methodToTestThatShouldReturnZero());

57

Giải pháp của vấn đề được gọi là sử dụng spy Mockito.spy (...) thay vì mock Mockito.mock (..) .

Spy cho phép chúng tôi chế nhạo một phần. Mockito là tốt trong vấn đề này. Bởi vì bạn có lớp chưa hoàn thành, theo cách này, bạn chế giễu một số vị trí bắt buộc trong lớp này.


3
Tôi tình cờ gặp ở đây vì tôi gặp vấn đề tương tự (cũng ngẫu nhiên, tình cờ đang thử nghiệm tương tác Chủ đề / Người quan sát). Tôi đã sử dụng một gián điệp nhưng tôi muốn phương pháp 'SubjectChanged' làm điều gì đó khác biệt. Tôi có thể sử dụng `verify (observer) .subjectChanged (chủ đề) chỉ để thấy rằng phương thức được gọi. Nhưng, vì một số lý do, tôi muốn thay thế phương thức. Vì thế, sự kết hợp giữa cách tiếp cận của
Sateesh

32
Không, làm điều này sẽ không thực sự giúp với các phương pháp nhạo báng. Bí quyết là sử dụng một trong bốn phương pháp tĩnh Mockito được liệt kê trong câu trả lời của sateesh.
Dawood ibn Kareem

2
@Gurnard cho câu hỏi của bạn hãy xem stackoverflow.com/questions/1087339/ này .
ibrahimyilmaz

Liều lượng này thực sự hoạt động.
Nilotpal

34

Trước hết: bạn phải luôn nhập mockito tĩnh, theo cách này, mã sẽ dễ đọc hơn (và trực quan):

import static org.mockito.Mockito.*;

Để chế nhạo một phần và vẫn giữ chức năng gốc trên phần còn lại, mockito cung cấp "Spy".

Bạn có thể sử dụng nó như sau:

private World world = spy(World.class);

Để loại bỏ một phương thức khỏi bị thực thi, bạn có thể sử dụng một cái gì đó như thế này:

doNothing().when(someObject).someMethod(anyObject());

để đưa ra một số hành vi tùy chỉnh cho một phương thức, sử dụng "khi" với "thenReturn":

doReturn("something").when(this.world).someMethod(anyObject());

Để biết thêm ví dụ xin vui lòng tìm các mẫu mockito tuyệt vời trong tài liệu.


4
Nhập tĩnh có liên quan gì để làm cho nó dễ đọc hơn?
jsonbourne

2
nghĩa đen là không có gì.
đặc biệt

2
Tôi nghĩ đó là vấn đề của hương vị, tôi chỉ muốn có câu lệnh (gần như) giống như một câu tiếng Anh và Class.methodname (). Something () so với methodname () .một cái gì đó dễ đọc hơn.
fl0w

1
Nếu bạn làm việc trong một nhóm, nhập tĩnh làm cho người khác cảm thấy khó chịu khi thấy phương thức này đến từ đâu, vì thường có một ký tự đại diện trong quá trình nhập.
LowKeyEnergy

đó là chính xác, tuy nhiên đối với mã Test và Mockito thì điều này là hiển nhiên và không phải là vấn đề.
fl0w

27

Cách mô phỏng các phương thức void với mockito - có hai tùy chọn:

  1. doAnswer - Nếu chúng tôi muốn phương thức void giả định của mình thực hiện điều gì đó (chế giễu hành vi mặc dù bị vô hiệu).
  2. doThrow- Sau đó, Mockito.doThrow()nếu bạn muốn ném một ngoại lệ từ phương thức void giả định.

Sau đây là một ví dụ về cách sử dụng nó (không phải là một usecase lý tưởng mà chỉ muốn minh họa cách sử dụng cơ bản).

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("old@test.com", "new@test.com");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("new@test.com")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("old@test.com", "new@test.com");

}
}

Bạn có thể tìm thêm chi tiết về cách giảkiểm tra các phương thức void với Mockito trong bài đăng của tôi Cách giả định với Mockito (Hướng dẫn toàn diện với các ví dụ)


1
Ví dụ tuyệt vời. Lưu ý: Trong java 8, có thể sẽ tốt hơn một chút khi sử dụng lambda thay vì một lớp đồng nghĩa: 'doAnswer ((Trả lời <Void>) invocation -> {// CODE}). Khi (mockInstance) .add (phương thức ()); '
miwe

16

Thêm một câu trả lời khác cho bó (không có ý định chơi chữ) ...

Bạn cần phải gọi phương thức doAnswer nếu bạn không muốn sử dụng spy. Tuy nhiên, bạn không nhất thiết phải cuộn Câu trả lời của riêng mình . Có một số triển khai mặc định. Đáng chú ý là CallsRealMethods .

Trong thực tế, nó trông giống như thế này:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

Hoặc là:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));

16

Trong Java 8, điều này có thể được làm sạch hơn một chút, giả sử bạn có một nhập tĩnh cho org.mockito.Mockito.doAnswer:

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

Điều return null;này rất quan trọng và nếu không có nó, trình biên dịch sẽ thất bại với một số lỗi khá tối nghĩa vì nó sẽ không thể tìm thấy ghi đè phù hợp chodoAnswer .

Ví dụ, một ExecutorServicecái mà ngay lập tức thực hiện bất kỳ Runnablethông qua nào execute()có thể được thực hiện bằng cách sử dụng:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());

2
Trong một dòng: Mockito.doAnswer ((i) -> null) .when (dụ) .method (any ());
Akshay Thorve

@AkshayThorve Điều đó không hoạt động khi bạn thực sự muốn làm mọi thứ với tôi.
Tim B

7

Tôi nghĩ vấn đề của bạn là do cấu trúc kiểm tra của bạn. Tôi đã gặp khó khăn khi trộn lẫn chế độ chế nhạo với phương pháp triển khai giao diện truyền thống trong lớp thử nghiệm (như bạn đã thực hiện ở đây).

Nếu bạn triển khai trình nghe dưới dạng Mock, thì bạn có thể xác minh sự tương tác.

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

Điều này sẽ thỏa mãn bạn rằng 'Thế giới' đang làm điều đúng đắn.


0

Sử dụng Mockito.doThrow như trong:

Mockito.doThrow(new Exception()).when(instance).methodName();

bạn có thể thử ví dụ hay này:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

Nguồn: http://apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

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.