Mockito Cách chỉ chế nhạo lệnh gọi của một phương thức của lớp cha


94

Tôi đang sử dụng Mockito trong một số thử nghiệm.

Tôi có các lớp sau:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

Tôi chỉ muốn chế nhạo cuộc gọi thứ hai ( super.save) của ChildService. Lần gọi đầu tiên phải gọi phương thức thực. Có cách nào làm được việc này không?


Điều này có thể được giải quyết với PowerMockito?
javaPlease 42

@ javaPlease42: Có, bạn có thể: stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

Câu trả lời:


57

Không, Mockito không hỗ trợ điều này.

Đây có thể không phải là câu trả lời bạn đang tìm kiếm, nhưng những gì bạn thấy là dấu hiệu của việc không áp dụng nguyên tắc thiết kế:

Thành phần ủng hộ hơn thừa kế

Nếu bạn trích xuất một chiến lược thay vì mở rộng một siêu lớp, vấn đề sẽ không còn nữa.

Tuy nhiên, nếu bạn không được phép thay đổi mã, nhưng bạn vẫn phải kiểm tra nó, và theo cách khó xử này, vẫn có hy vọng. Với một số công cụ AOP (ví dụ: AspectJ), bạn có thể đan mã vào phương thức siêu lớp và tránh hoàn toàn việc thực thi nó (yuck). Điều này không hiệu quả nếu bạn đang sử dụng proxy, bạn phải sử dụng sửa đổi mã bytecode (dệt thời gian tải hoặc dệt thời gian biên dịch). Có những khuôn khổ chế nhạo cũng hỗ trợ loại thủ thuật này, như PowerMock và PowerMockito.

Tôi khuyên bạn nên tiến hành tái cấu trúc, nhưng nếu đó không phải là một lựa chọn, bạn đang ở trong một số trò vui hack nghiêm trọng.


5
Tôi không thấy vi phạm LSP. Tôi có thiết lập gần giống với OP: một lớp DAO cơ sở với phương thức findAll () và một lớp con DAO ghi đè phương thức cơ sở bằng cách gọi super.findAll () và sau đó sắp xếp kết quả. Lớp con có thể thay thế trong tất cả các ngữ cảnh chấp nhận lớp cha. Tôi đang hiểu sai ý của bạn?

1
Tôi sẽ xóa nhận xét LSP (nó không thêm giá trị cho câu trả lời).
iwein

Đúng là thừa kế tệ hại và khung công tác ngu ngốc mà tôi đang mắc kẹt được thiết kế với kế thừa là lựa chọn duy nhất.
Sridhar Sarnobat

Giả sử bạn không thể thiết kế lại lớp cha, bạn có thể trích xuất //some codesmã thành một phương pháp có thể được kiểm tra riêng.
Phasmal

1
OK đã nhận nó. Đó là một vấn đề khác với những gì tôi đã cố gắng giải quyết khi tôi tìm kiếm điều này, nhưng ít nhất thì sự hiểu lầm đó đã giải quyết được vấn đề của chính tôi để chế nhạo cuộc gọi từ lớp cơ sở (mà tôi thực sự không ghi đè).
Guillaume Perrot

86

Nếu bạn thực sự không có lựa chọn để tái cấu trúc, bạn có thể mô phỏng / khai thác mọi thứ trong lệnh gọi siêu phương thức, ví dụ:

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

2
mã này sẽ không thực sự ngăn chặn sự super.save () gọi ngay, vì vậy nếu bạn làm được rất nhiều trong super.save (), bạn sẽ phải ngăn chặn tất cả những cuộc gọi ...
iwein

giải pháp tuyệt vời làm việc kỳ diệu cho tôi khi tôi muốn trả về một giá trị bị chế nhạo từ một phương thức siêu lớp để đứa trẻ sử dụng, thật tuyệt, cảm ơn bạn.
Gurnard

14
điều này hoạt động tốt trừ khi xác thực bị ẩn hoặc phương thức lưu hoạt động trực tiếp thay vì gọi một phương thức khác. mockito không: Mockito.doNothing (). when ((BaseService) spy) .save (); điều này sẽ không 'DoNothing trên các dịch vụ cơ sở tiết kiệm nhưng trên childService lưu :(
Tibi

Tôi không thể làm cho điều này hoạt động trên của tôi - nó cũng khai ra phương pháp con. BaseServicelà trừu tượng, mặc dù tôi không hiểu tại sao điều đó lại có liên quan.
Sridhar Sarnobat

1
@ Sridhar-Sarnobat yea tôi đang nhìn thấy những điều tương tự :( ai biết làm thế nào để làm cho nó chỉ còn sơ khai ra super.validate()?
stantonk

4

Hãy xem xét việc cấu trúc lại mã từ phương thức ChildService.save () thành phương thức khác và kiểm tra phương thức mới đó thay vì thử nghiệm ChildService.save (), bằng cách này bạn sẽ tránh được việc gọi siêu phương thức không cần thiết.

Thí dụ:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

1

tạo một phương thức được bảo vệ bằng gói (giả sử lớp thử nghiệm trong cùng một gói) trong lớp con gọi phương thức siêu lớp và sau đó gọi phương thức đó trong phương thức lớp con bị ghi đè của bạn. sau đó bạn có thể đặt kỳ vọng vào phương pháp này trong thử nghiệm của mình thông qua việc sử dụng mẫu gián điệp. không đẹp nhưng chắc chắn tốt hơn là phải đối phó với tất cả các kỳ vọng đặt ra cho phương pháp siêu việt trong bài kiểm tra của bạn


tôi cũng có thể nói rằng thành phần kế thừa hầu như luôn luôn tốt hơn, nhưng đôi khi nó chỉ đơn giản là đơn giản hơn để sử dụng kế thừa. cho đến khi java kết hợp một mô hình tổng hợp tốt hơn, như scala hoặc groovy, điều này sẽ luôn xảy ra và vấn đề này sẽ tiếp tục tồn tại
Luke

1

Ngay cả khi tôi hoàn toàn đồng ý với phản hồi của iwein (

thành phần ưu tiên hơn thừa kế

), tôi thừa nhận rằng có một số lần kế thừa dường như chỉ là tự nhiên và tôi không cảm thấy bị phá vỡ hoặc cấu trúc lại nó chỉ vì lợi ích của một bài kiểm tra đơn vị.

Vì vậy, gợi ý của tôi:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

Và sau đó, trong bài kiểm tra đơn vị:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done

0

Lý do là lớp cơ sở của bạn không phải là public-ed, sau đó Mockito không thể chặn nó do khả năng hiển thị, nếu bạn thay đổi lớp cơ sở thành public hoặc @Override trong lớp con (dưới dạng công khai), thì Mockito có thể mô phỏng nó một cách chính xác.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

8
Đây không phải là vấn đề. ChildService nên ghi đè foo () và vấn đề là làm thế nào để chế nhạo BaseService.foo () nhưng không ChildService.foo ()
Adriaan Koster

0

Có thể tùy chọn dễ dàng nhất nếu kế thừa có ý nghĩa là tạo một phương thức mới (gói riêng tư ??) để gọi super (cho phép gọi nó là superFindall), theo dõi phiên bản thực và sau đó giả lập phương thức superFindAll () theo cách bạn muốn mô phỏng lớp cha một. Nó không phải là giải pháp hoàn hảo về mức độ phủ sóng và khả năng hiển thị nhưng nó sẽ hoạt động tốt và rất dễ áp ​​dụng.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}
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.