Khởi tạo các đối tượng giả - MockIto


122

Có nhiều cách để khởi tạo một đối tượng giả bằng MockIto. Cách tốt nhất trong số này là gì?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2.

@RunWith(MockitoJUnitRunner.class)

[CHỈNH SỬA] 3.

mock(XXX.class);

gợi ý cho tôi nếu có bất kỳ cách nào khác tốt hơn những cách này ...

Câu trả lời:


153

Đối với khởi tạo mocks , sử dụng runner hoặc các MockitoAnnotations.initMocksgiải pháp tương đương. Từ javadoc của MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


Giải pháp đầu tiên (với MockitoAnnotations.initMocks) có thể được sử dụng khi bạn đã định cấu hình một trình chạy cụ thể ( SpringJUnit4ClassRunnerví dụ) trên trường hợp thử nghiệm của bạn.

Giải pháp thứ hai (với MockitoJUnitRunner) là giải pháp cổ điển hơn và yêu thích của tôi. Mã đơn giản hơn. Sử dụng một trình chạy mang lại lợi thế lớn là xác thực tự động việc sử dụng khung (được mô tả bởi @David Wallace trong câu trả lời này ).

Cả hai giải pháp đều cho phép chia sẻ các mô phỏng (và gián điệp) giữa các phương pháp thử nghiệm. Cùng với @InjectMocks, chúng cho phép viết các bài kiểm tra đơn vị rất nhanh chóng. Mã chế giễu chương trình được giảm bớt, các bài kiểm tra dễ đọc hơn. Ví dụ:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Ưu điểm: Mã tối thiểu

Nhược điểm: Ma thuật đen. IMO chủ yếu là do chú thích @InjectMocks. Với chú thích này "bạn đã giải phóng nỗi đau của mã" (xem các nhận xét tuyệt vời của @Brice )


Giải pháp thứ ba là tạo mô hình thử nghiệm của bạn theo từng phương pháp kiểm tra. Nó cho phép như được giải thích bởi @mlk trong câu trả lời của nó để có " kiểm tra độc lập ".

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Ưu điểm: Bạn chứng minh rõ ràng cách api của bạn hoạt động (BDD ...)

Nhược điểm: có nhiều mã viết sẵn hơn. (Sáng tạo chế giễu)


Lời khuyên của tôi là một sự thỏa hiệp. Sử dụng @Mockchú thích với @RunWith(MockitoJUnitRunner.class), nhưng không sử dụng @InjectMocks:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Ưu điểm: Bạn chứng minh rõ ràng cách api của bạn hoạt động (Cách ArticleManagerkhởi tạo của tôi ). Không có mã ghi sẵn.

Nhược điểm: Thử nghiệm không khép kín, ít mã


Mặc dù vậy, hãy cẩn thận, các chú thích hữu ích nhưng chúng không bảo vệ bạn khỏi việc tạo ra thiết kế OO kém (hoặc làm suy giảm nó). Cá nhân tôi trong khi tôi rất vui khi giảm thiểu mã viết sẵn, tôi đã bỏ qua nỗi đau của mã (hoặc PITA) là yếu tố kích hoạt để thay đổi thiết kế thành tốt hơn, vì vậy tôi và nhóm đang chú ý đến thiết kế OO. Tôi cảm thấy rằng việc tuân theo thiết kế OO với các nguyên tắc như thiết kế SOLID hoặc ý tưởng GOOS quan trọng hơn nhiều so với việc chọn cách khởi tạo mocks.
Brice

1
(theo dõi) Nếu bạn không thấy đối tượng này được tạo ra như thế nào, bạn không cảm thấy đau đớn về nó và các lập trình viên trong tương lai có thể phản ứng không tốt nếu chức năng mới nên được thêm vào. Dù sao thì đó là cả hai cách, tôi chỉ nói rằng hãy cẩn thận về nó.
Brice

6
KHÔNG ĐÚNG rằng hai cái này tương đương nhau. Không ĐÚNG rằng mã đơn giản hơn là lợi thế duy nhất để sử dụng MockitoJUnitRunner. Để biết thêm thông tin về sự khác biệt, hãy xem câu hỏi tại stackoverflow.com/questions/10806345/… và câu trả lời của tôi cho nó.
Dawood ibn Kareem

2
@Gontard Vâng chắc chắn có thể nhìn thấy các phần phụ thuộc, nhưng tôi đã thấy mã bị sai khi sử dụng cách tiếp cận này. Về việc sử dụng Collaborator collab = mock(Collaborator.class), theo tôi cách này chắc chắn là một cách tiếp cận hợp lệ. Mặc dù điều này có xu hướng dài dòng, nhưng bạn có thể hiểu được và khả năng tái cấu trúc của các bài kiểm tra. Cả hai cách đều có ưu và nhược điểm, tôi vẫn chưa quyết định cách tiếp cận nào tốt hơn. Amyway luôn có thể viết những thứ tào lao, và có lẽ phụ thuộc vào bối cảnh và người viết mã.
Brice

1
@mlk tôi hoàn toàn đồng ý với bạn. Tiếng Anh của tôi không tốt lắm và nó thiếu sắc thái. Quan điểm của tôi là nhấn mạnh vào từ UNIT.
gontard

30

Hiện tại (kể từ v1.10.7) có một cách thứ tư để tạo chế độ giả, đó là sử dụng quy tắc JUnit4 được gọi là MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit tìm kiếm các lớp con của TestRule được chú thích bằng @Rule và sử dụng chúng để bọc các Câu lệnh thử nghiệm mà Runner cung cấp . Kết quả của việc này là bạn có thể trích xuất các phương thức @Before, phương thức @After và thậm chí thử ... bắt các trình bao bọc thành các quy tắc. Bạn thậm chí có thể tương tác với những thứ này từ bên trong thử nghiệm của mình, theo cách mà mong đợi Exception thực hiện.

MockitoRule hoạt động gần giống như MockitoJUnitRunner , ngoại trừ việc bạn có thể sử dụng bất kỳ trình chạy nào khác, chẳng hạn như Parameterized (cho phép các trình tạo kiểm thử của bạn nhận các đối số để các kiểm thử của bạn có thể được chạy nhiều lần) hoặc trình chạy kiểm tra của Robolectric (để trình nạp lớp của nó có thể cung cấp các thay thế Java cho các lớp gốc Android). Điều này làm cho nó linh hoạt hơn để sử dụng trong các phiên bản JUnit và Mockito gần đây.

Tóm tắt:

  • Mockito.mock(): Lời gọi trực tiếp không hỗ trợ chú thích hoặc xác thực sử dụng.
  • MockitoAnnotations.initMocks(this): Hỗ trợ chú thích, không có xác thực sử dụng.
  • MockitoJUnitRunner: Hỗ trợ chú thích và xác thực sử dụng, nhưng bạn phải sử dụng trình chạy đó.
  • MockitoRule: Hỗ trợ chú thích và xác thực sử dụng với bất kỳ trình chạy JUnit nào.

Xem thêm: JUnit @Rule hoạt động như thế nào?


3
Trong Kotlin, quy tắc trông như thế này:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan 14/03/18

10

Có một cách gọn gàng để làm điều này.

  • Nếu đó là Bài kiểm tra đơn vị, bạn có thể làm điều này:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
  • CHỈNH SỬA: Nếu đó là một bài kiểm tra Tích hợp, bạn có thể thực hiện việc này (không nhằm mục đích sử dụng theo cách đó với Spring. Chỉ cần giới thiệu rằng bạn có thể khởi tạo mô hình giả với các Người chạy khác nhau):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }

1
Nếu MOCK cũng tham gia vào các bài kiểm tra Tích hợp, nó có hợp lý không?
VinayVeluri 19/03/2013

2
thực sự nó sẽ không, quyền của bạn. Tôi chỉ muốn thể hiện khả năng của Mockito. Ví dụ: nếu việc sử dụng RESTFuse của bạn, bạn phải sử dụng trình chạy của họ để bạn có thể khởi tạo các mô phỏng bằng MockitoAnnotations.initMocks (this);
emd

8

Mô tả:

XXX mockedXxx = mock(XXX.class);

Tôi sử dụng điều này vì tôi thấy nó mang tính mô tả nhiều hơn một chút và tôi thích các thử nghiệm đơn vị (không phải là cấm ngay) không sử dụng các biến thành viên vì tôi muốn các thử nghiệm của mình (càng nhiều càng tốt) độc lập.


Có lợi thế nào khác so với việc sử dụng mock (XX.class) ngoại trừ việc làm cho trường hợp thử nghiệm trở nên độc lập không?
VinayVeluri 19/03/2013

Không xa như tôi biết.
Michael Lloyd Lee mlk

3
Ít ma thuật cần phải hiểu để đọc bài kiểm tra. Bạn khai báo các biến, và cung cấp cho nó một giá trị - không có chú thích, phản xạ, vv
Karu

2

Một ví dụ nhỏ cho JUnit 5 Jupiter, "RunWith" đã bị xóa, bây giờ bạn cần sử dụng Phần mở rộng bằng cách sử dụng Chú thích "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

0

Các câu trả lời khác rất tuyệt và chứa nhiều chi tiết hơn nếu bạn muốn / cần chúng.
Ngoài những thứ đó, tôi muốn thêm TL; DR:

  1. Thích sử dụng
    • @RunWith(MockitoJUnitRunner.class)
  2. Nếu bạn không thể (vì bạn đã sử dụng một người chạy khác), hãy thích sử dụng
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Tương tự như (2), nhưng bạn không nên sử dụng nó nữa:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Nếu bạn muốn sử dụng mô hình chỉ trong một trong các bài kiểm tra và không muốn để lộ nó cho các bài kiểm tra khác trong cùng một lớp kiểm tra, hãy sử dụng
    • X x = mock(X.class)

(1) và (2) và (3) loại trừ lẫn nhau.
(4) có thể được sử dụng kết hợp với những người khác.

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.