Mockito: làm thế nào để xác minh phương thức được gọi trên một đối tượng được tạo trong một phương thức?


322

Tôi mới đến Mockito.

Với lớp bên dưới, làm cách nào tôi có thể sử dụng Mockito để xác minh rằng someMethodđã được gọi chính xác một lần sau khi foođược gọi?

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

Tôi muốn thực hiện cuộc gọi xác minh sau đây,

verify(bar, times(1)).someMethod();

nơi barlà một trường hợp chế giễu của Bar.


2
stackoverflow.com/questions/6520242/ cấp - Nhưng tôi không muốn sử dụng PowerMock.
MRE

Thay đổi API hoặc PowerMock. Một trong hai.
John B

Làm thế nào để bao gồm một cái gì đó như thế này ?? void start được đồng bộ hóa công khai (BundleContext bundleContext) ném Exception {BundleContext bc = bundleContext; logger.info ("BẮT ĐẦU DỊCH VỤ HTTP BẮT ĐẦU"); this registerServlets (); trả lại dịch vụ http; }}}
ShAkKiR

Câu trả lời:


366

Phụ thuộc tiêm

Nếu bạn tiêm đối tượng Bar hoặc nhà máy được sử dụng để tạo đối tượng Bar (hoặc một trong 483 cách khác để thực hiện việc này), bạn sẽ có quyền truy cập cần thiết để thực hiện kiểm tra.

Ví dụ về nhà máy:

Đưa ra một lớp Foo được viết như thế này:

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

trong phương pháp thử nghiệm của bạn, bạn có thể tiêm BarFactory như thế này:

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

Phần thưởng: Đây là một ví dụ về cách TDD có thể thúc đẩy thiết kế mã của bạn.


6
Có cách nào để làm điều này mà không sửa đổi lớp để kiểm tra đơn vị không?
MRE

6
Bar bar = mock(Bar.class)thay vìBar bar = new Bar();
John B

7
không phải là tôi biết. nhưng, tôi không đề nghị bạn sửa đổi lớp chỉ để kiểm tra đơn vị. Đây thực sự là một cuộc trò chuyện về mã sạch và SRP. Hoặc .. đó là trách nhiệm của phương thức foo () trong lớp Foo để xây dựng một đối tượng Bar. Nếu câu trả lời là có, thì đó là chi tiết triển khai và bạn không nên lo lắng về việc kiểm tra tương tác cụ thể (tham khảo câu trả lời của @ Michael). Nếu câu trả lời là không, thì bạn đang sửa đổi lớp vì khó kiểm tra của bạn là một lá cờ đỏ mà thiết kế của bạn cần cải thiện một chút (do đó phần thưởng tôi đã thêm vào cách thiết kế ổ đĩa TDD).
csturtz

3
Bạn có thể chuyển một đối tượng "thực" cho "xác minh" của Mockito không?
John B

4
Bạn cũng có thể chế giễu nhà máy: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa

18

Câu trả lời kinh điển là "Bạn không." Bạn kiểm tra API công khai của Foo, không phải nội bộ của nó.

Có bất kỳ hành vi nào của Foođối tượng (hoặc, kém tốt hơn, một số đối tượng khác trong môi trường) bị ảnh hưởng foo()không? Nếu vậy, kiểm tra điều đó. Và nếu không, phương pháp này làm gì?


4
Vì vậy, những gì bạn thực sự sẽ kiểm tra ở đây? API công khai Foopublic void foo(), trong đó các phần bên trong chỉ liên quan đến thanh.
xử

15
Chỉ kiểm tra API công khai là ổn, cho đến khi có lỗi chính hãng với các tác dụng phụ cần kiểm tra. Ví dụ: kiểm tra xem một phương thức riêng tư đang đóng đúng các kết nối HTTP của nó có quá mức cho đến khi bạn phát hiện ra rằng phương thức riêng đó không đóng các kết nối của nó đúng cách và do đó gây ra một vấn đề lớn. Vào thời điểm đó, Mockito và verify()thực sự trở nên rất hữu ích, ngay cả khi bạn không còn thờ phượng tại bàn thờ thánh của thử nghiệm tích hợp.
Dawngerpony

@DuffJ Tôi không sử dụng Java, nhưng nghe có vẻ như thứ gì đó mà trình biên dịch hoặc công cụ phân tích mã của bạn sẽ phát hiện ra.
user247702

3
Tôi đồng ý với DuffJ, trong khi lập trình chức năng rất thú vị, có một điểm mà mã của bạn tương tác với thế giới bên ngoài. Không quan trọng nếu bạn gọi nó là "nội bộ", "tác dụng phụ" hoặc "chức năng", bạn chắc chắn muốn kiểm tra sự tương tác đó: nếu nó xảy ra và nếu nó xảy ra đúng số lần và với các đối số chính xác. @Stijn: nó có thể là một ví dụ tồi (nhưng nếu nhiều kết nối nên được mở và chỉ một số trong số chúng bị đóng, thì điều đó trở nên thú vị). Một ví dụ tốt hơn là kiểm tra thời tiết dữ liệu chính xác sẽ được gửi qua kết nối.
Andras Balázs Lajtha

13

Nếu bạn không muốn sử dụng DI hoặc Factories. Bạn có thể cấu trúc lại lớp của mình theo một cách khó khăn:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

Và lớp kiểm tra của bạn:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

Sau đó, lớp đang gọi phương thức foo của bạn sẽ làm như thế này:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

Như bạn có thể thấy khi gọi phương thức theo cách này, bạn không cần nhập lớp Bar trong bất kỳ lớp nào khác đang gọi phương thức foo của bạn, đây có thể là thứ bạn muốn.

Tất nhiên nhược điểm là bạn đang cho phép người gọi đặt Đối tượng Bar.

Hy vọng nó giúp.


3
Tôi nghĩ rằng đây là một mô hình chống. Phụ thuộc nên được tiêm, thời gian. Cho phép một phụ thuộc được tiêm tùy chọn chỉ nhằm mục đích thử nghiệm là cố tình tránh cải thiện mã và đang cố ý thử nghiệm một cái gì đó khác với mã chạy trong sản xuất. Cả hai đều là những điều khủng khiếp, khủng khiếp để làm.
ErikE

8

Giải pháp cho mã ví dụ của bạn bằng cách sử dụng PowerMockito.whenNew

  • mockito-tất cả 1.10.8
  • lõi điện 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • tháng sáu 4,12

FooTest.java

package foo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

Đầu ra JUnit Đầu ra JUnit


8

Tôi nghĩ Mockito @InjectMockslà con đường để đi.

Tùy thuộc vào ý định của bạn, bạn có thể sử dụng:

  1. Xây dựng tiêm
  2. Tài sản setter tiêm
  3. Lĩnh vực tiêm

Thêm thông tin trong tài liệu

Dưới đây là một ví dụ với tiêm trường:

Các lớp học:

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

Kiểm tra:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}

3

Có, nếu bạn thực sự muốn / cần làm điều đó, bạn có thể sử dụng PowerMock. Điều này nên được coi là một phương sách cuối cùng. Với PowerMock, bạn có thể khiến nó trả về một bản giả từ cuộc gọi đến hàm tạo. Sau đó làm xác minh trên giả. Điều đó nói rằng, csturtz là câu trả lời "đúng".

Đây là liên kết đến Mock xây dựng các đối tượng mới


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.