Làm thế nào để chế nhạo một lớp cuối cùng với mockito


218

Tôi có một lớp cuối cùng, đại loại như thế này:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Tôi đang sử dụng lớp này trong một số lớp khác như thế này:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

và trong lớp kiểm tra JUnit của Seasons.javatôi, tôi muốn chế giễu RainOnTreeslớp học. Làm thế nào tôi có thể làm điều này với Mockito?


9
Mockito không cho phép, tuy nhiên PowerMock thì có.
fge

1
Kể từ Mockito 2.x, Mockito hiện hỗ trợ chế nhạo các lớp và phương thức cuối cùng.
Kent

Có thể trùng lặp lớp cuối cùng
eliasah

Câu trả lời:


155

Việc mô phỏng các lớp / phương thức tĩnh / cuối chỉ có thể với Mockito v2.

thêm phần này vào tập tin lớp của bạn:

testImplementation 'org.mockito:mockito-inline:2.13.0'

Điều này là không thể với Mockito v1, từ Câu hỏi thường gặp về Mockito :

Những hạn chế của Mockito là gì

  • Cần java 1.5+

  • Không thể giả lớp cuối cùng

...


2
Điều này không làm việc cho tôi trong Scala (với sửa đổi sbt).
micseydel

2
Điều này là không đủ cho tôi. Tôi cũng phải tạo src / test / resource / mockito-extend / org.mockito.plugins.MockMaker với "mock-Maker-inline" trong đó theo baeldung.com/mockito-final
micseydel 22/10/19

204

Mockito 2 hiện hỗ trợ các lớp và phương thức cuối cùng !

Nhưng bây giờ, đó là một tính năng "ủ bệnh". Nó yêu cầu một số bước để kích hoạt nó được mô tả trong What New trong Mockito 2 :

Mocking các lớp và phương thức cuối cùng là một tính năng ươm tạo , chọn tham gia. Nó sử dụng kết hợp các thiết bị đại lý Java và phân lớp để cho phép khả năng giả định của các loại này. Vì cơ chế này hoạt động khác với cơ chế hiện tại của chúng tôi và cơ chế này có những hạn chế khác nhau và vì chúng tôi muốn thu thập kinh nghiệm và phản hồi của người dùng, tính năng này phải được kích hoạt rõ ràng để có sẵn; nó có thể được thực hiện thông qua cơ chế mở rộng mockito bằng cách tạo tệp src/test/resources/mockito-extensions/org.mockito.plugins.MockMakerchứa một dòng duy nhất:

mock-maker-inline

Sau khi bạn tạo tệp này, Mockito sẽ tự động sử dụng công cụ mới này và người ta có thể làm:

 final class FinalClass {
   final String finalMethod() { return "something"; }
 }

 FinalClass concrete = new FinalClass(); 

 FinalClass mock = mock(FinalClass.class);
 given(mock.finalMethod()).willReturn("not anymore");

 assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

Trong các cột mốc tiếp theo, nhóm sẽ mang đến một cách lập trình sử dụng tính năng này. Chúng tôi sẽ xác định và cung cấp hỗ trợ cho tất cả các kịch bản không thể thay đổi. Hãy theo dõi và xin vui lòng cho chúng tôi biết bạn nghĩ gì về tính năng này!


14
Tôi vẫn gặp lỗi: Không thể mock / lớp gián điệp android.content.ComponentName Mockito không thể giả lập / gián điệp vì: - lớp cuối cùng
IgorGanapolsky

3
Hãy chắc chắn rằng bạn đặt org.mockito.plugins.MockMakertập tin vào đúng thư mục.
WindRider

7
Tôi cũng nhận được lỗi ngay cả sau khi làm theo các điều đã đề cập ở trên: Mockito không thể chế nhạo / gián điệp vì: - lớp cuối cùng
RCde0

8
@vCillusion câu trả lời này không liên quan đến PowerMock dưới bất kỳ hình thức nào.
Dòng

6
Tôi đã làm theo các hướng dẫn này nhưng tôi vẫn không thể thực hiện công việc này, có ai phải làm gì khác không?
Franco

43

Bạn không thể chế nhạo một lớp cuối cùng với Mockito, vì bạn không thể tự mình làm điều đó.

Những gì tôi làm, là tạo ra một lớp không phải là cuối cùng để bọc lớp cuối cùng và sử dụng làm đại biểu. Một ví dụ về điều này là TwitterFactorylớp và đây là lớp có thể nhạo báng của tôi:

public class TwitterFactory {

    private final twitter4j.TwitterFactory factory;

    public TwitterFactory() {
        factory = new twitter4j.TwitterFactory();
    }

    public Twitter getInstance(User user) {
        return factory.getInstance(accessToken(user));
    }

    private AccessToken accessToken(User user) {
        return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
    }

    public Twitter getInstance() {
        return factory.getInstance();
    }
}

Nhược điểm là có rất nhiều mã soạn sẵn; lợi thế là bạn có thể thêm một số phương thức có thể liên quan đến doanh nghiệp ứng dụng của bạn (như getInstance đang lấy người dùng thay vì accessToken, trong trường hợp trên).

Trong trường hợp của bạn, tôi sẽ tạo một RainOnTreeslớp không phải là lớp cuối cùng ủy nhiệm cho lớp cuối cùng. Hoặc, nếu bạn có thể làm cho nó không phải là cuối cùng, nó sẽ tốt hơn.


6
+1. Nếu muốn, bạn có thể sử dụng một cái gì đó như Lombok @Delegateđể xử lý nhiều nồi hơi.
ruakh

2
@luigi bạn có thể thêm đoạn mã cho Junit làm ví dụ. Tôi đã cố gắng tạo Wrapper cho lớp cuối cùng của mình, nhưng không thể tìm ra cách kiểm tra nó.
Không thể tin được vào

31

thêm phần này vào tập tin lớp của bạn:

testImplementation 'org.mockito:mockito-inline:2.13.0'

đây là cấu hình để làm cho mockito hoạt động với các lớp cuối cùng


1
Có lẽ nên sử dụng "testImcellenceation" ngay bây giờ thay vì "testCompile". Gradle không thích "testCompile" nữa.
jwehrle

bình luận tuyệt vời, cảm ơn! chỉnh sửa để kiểm tra thực hiện. nhận xét ban đầu: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP

2
Điều này dẫn đến lỗi khi chạy trên Linux / OpenJDK 1.8:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa

Hoạt động tốt khi chuyển sang Oracle JDK 1.8
naXa

23

Sử dụng Powermock. Liên kết này hiển thị, cách thực hiện: https://github.com/jayway/powermock/wiki/MockFinal


30
Tôi nghĩ PowerMock giống như một trong những loại thuốc chỉ nên dùng trên cơ sở "theo toa". Theo nghĩa: người ta phải nói rõ rằng PowerMock có rất nhiều vấn đề; và việc sử dụng nó giống như phương sách cuối cùng; và nên tránh càng nhiều càng tốt.
GhostCat

1
tại sao bạn nói như vậy?
thực dụng

Tôi đã sử dụng Powermockđể chế nhạo các lớp cuối cùng và các phương thức tĩnh để tăng phạm vi bảo hiểm đã được kiểm tra chính thức Sonarqube. Bảo hiểm là 0% kể từ SonarQube, vì lý do chưa từng nhận ra các lớp sử dụng Powermock ở bất cứ đâu trong đó. Tôi đã mất một thời gian để nhận ra nó từ một số chủ đề trực tuyến. Vì vậy, đó chỉ là một trong những lý do để cẩn thận với Powermock và có thể không sử dụng nó.
amer

16

Chỉ để theo dõi. Vui lòng thêm dòng này vào tập tin lớp của bạn:

testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'

Tôi đã thử nhiều phiên bản khác nhau của mockito-core và mockito-all. Không ai trong số họ làm việc.


1
Để thêm vào điều này, một điều tôi quan sát được là nếu bạn đang sử dụng Powermock cùng với mockito; sau đó thêm tệp plugin mockmaker vào 'src / test / resource / mockito-extend / org.mockito.plugins.MockMaker' sẽ không hữu ích trong việc chế nhạo các lớp cuối cùng. Thay vào đó, việc thêm một phụ thuộc như được đề cập bởi Michael_Zhang ở trên sẽ giải quyết vấn đề chế nhạo các lớp cuối cùng. Ngoài ra, hãy chắc chắn rằng bạn đang sử dụng Mockito 2 thay vì Mockito1
món ăn

12

Tôi đoán bạn đã làm nó finalbởi vì bạn muốn ngăn chặn các lớp khác mở rộng RainOnTrees. Như Java hiệu quả đề xuất (mục 15), có một cách khác để giữ một lớp đóng để mở rộng mà không cần thực hiện final:

  1. Xóa finaltừ khóa;

  2. Làm cho nhà xây dựng của nó private. Không có lớp nào có thể mở rộng nó bởi vì nó sẽ không thể gọi hàm supertạo;

  3. Tạo một phương thức nhà máy tĩnh để khởi tạo lớp của bạn.

    // No more final keyword here.
    public class RainOnTrees {
    
        public static RainOnTrees newInstance() {
            return new RainOnTrees();
        }
    
    
        private RainOnTrees() {
            // Private constructor.
        }
    
        public void startRain() {
    
            // some code here
        }
    }

Bằng cách sử dụng chiến lược này, bạn sẽ có thể sử dụng Mockito và đóng lớp của bạn để mở rộng với ít mã soạn sẵn.


1
điều này không hoạt động đối với các phương thức cuối cùng mà với mockito 2 cũng có thể bị chế giễu.
ukasz Rzeszotarski

11

Tôi đã từng gặp vấn đề tương tự. Vì lớp tôi đang cố gắng giả là một lớp đơn giản, tôi chỉ cần tạo một thể hiện của nó và trả về nó.


2
Tuyệt đối, tại sao lại chế nhạo một lớp học đơn giản? Mocking là dành cho các tương tác 'đắt tiền': các dịch vụ khác, động cơ, lớp dữ liệu, v.v.
StripLight

3
Nếu bạn tạo một thể hiện của điều đó, bạn không thể áp dụng các phương thức Mockito.verify trên đó sau đó. Công dụng chính của giả là có thể kiểm tra một số phương pháp của nó.
riroo

6

Hãy thử xem:

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

Nó làm việc cho tôi. "SomeMockableType. Class" là lớp cha của những gì bạn muốn chế giễu hoặc gián điệp, và someInstanceThatIsNotMockableOrSpyable là lớp thực tế mà bạn muốn chế giễu hoặc gián điệp.

Để biết thêm chi tiết có một cái nhìn ở đây


3
Cần lưu ý rằng các đại biểu rất khác với chế giễu gián điệp bản địa. Trong một gián điệp mockito bản địa, "cái này" trong trường hợp tham chiếu đến chính điệp viên đó (vì đang sử dụng lớp con) Tuy nhiên, trong đại biểu, "cái này" sẽ là đối tượng thực sự someInstanceThatIsNotMockableOrSpyable. Không phải gián điệp. Vì vậy, không có cách nào để thực hiện / xác minh cho các chức năng tự gọi.
Dennis C

1
bạn có thể đưa ra một ví dụ?
Vishwa Ratna

5

Một cách giải quyết khác, có thể áp dụng trong một số trường hợp, là tạo giao diện được thực hiện bởi lớp cuối cùng đó, thay đổi mã để sử dụng giao diện thay vì lớp cụ thể và sau đó giả định giao diện. Điều này cho phép bạn tách hợp đồng (giao diện) khỏi việc thực hiện (lớp cuối cùng). Tất nhiên, nếu những gì bạn muốn thực sự liên kết với lớp cuối cùng, điều này sẽ không được áp dụng.


5

Thật ra có một cách, mà tôi dùng để làm gián điệp. Nó sẽ chỉ làm việc cho bạn nếu hai điều kiện tiên quyết được thỏa mãn:

  1. Bạn sử dụng một số loại DI để tiêm một thể hiện của lớp cuối cùng
  2. Lớp cuối cùng thực hiện một giao diện

Vui lòng nhớ lại Mục 16 từ Java hiệu quả . Bạn có thể tạo một trình bao bọc (không phải là cuối cùng) và chuyển tiếp tất cả các cuộc gọi đến thể hiện của lớp cuối cùng:

public final class RainOnTrees implement IRainOnTrees {
    @Override public void startRain() { // some code here }
}

public class RainOnTreesWrapper implement IRainOnTrees {
    private IRainOnTrees delegate;
    public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
    @Override public void startRain() { delegate.startRain(); }
}

Bây giờ bạn không chỉ có thể chế giễu lớp cuối cùng của mình mà còn theo dõi nó:

public class Seasons{
    RainOnTrees rain;
    public Seasons(IRainOnTrees rain) { this.rain = rain; };
    public void findSeasonAndRain(){
        rain.startRain();
   }
}

IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();

5

Trong Mockito 3 trở lên tôi có cùng một vấn đề và đã sửa nó từ liên kết này

Mockito Lớp học và Phương pháp cuối cùng với Mockito như sau

Trước khi Mockito có thể được sử dụng để chế nhạo các lớp và phương thức cuối cùng, nó cần phải được cấu hình>.

Chúng ta cần thêm một tệp văn bản vào thư mục src / test / resource / mockito-extend của dự án có tên org.mockito.plugins.MockMaker và thêm một dòng văn bản:

mock-maker-inline

Mockito kiểm tra thư mục tiện ích mở rộng cho các tệp cấu hình khi được tải. Tập tin này cho phép chế nhạo các phương thức và lớp cuối cùng.


4

Tiết kiệm thời gian cho những người đang phải đối mặt với cùng một vấn đề (Mockito + Final Class) trên Android + Kotlin. Như trong các lớp học Kotlin là cuối cùng theo mặc định. Tôi đã tìm thấy một giải pháp trong một trong các mẫu Google Android có thành phần Architecture. Giải pháp được chọn từ đây: https://github.com/googlesamples/android-arch architecture-component / blog / master / GithubBrowserSample

Tạo các chú thích sau:

/**
 * This annotation allows us to open some classes for mocking purposes while they are final in
 * release builds.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass

/**
 * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
 */
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting

Sửa đổi tập tin lớp của bạn. Lấy ví dụ từ đây: https://github.com/googlesamples/android-arch architecture -compents / blog / master / GithubBrowserSample / app / build.gradle

apply plugin: 'kotlin-allopen'

allOpen {
    // allows mocking for classes w/o directly opening them for release builds
    annotation 'com.android.example.github.testing.OpenClass'
}

Bây giờ bạn có thể chú thích bất kỳ lớp nào để mở nó để thử nghiệm:

@OpenForTesting
class RepoRepository 

Điều này hoạt động tốt ở cấp độ ứng dụng build.gradle nhưng chúng ta có thể làm gì để có được điều này ở cấp thư viện?
Sumit T

Bạn có thể xây dựng một chút? Thông thường, sử dụng mô hình mặt tiền để kết nối với libs. Và chế nhạo các lớp mặt tiền này để kiểm tra ứng dụng. Theo cách này, chúng ta không cần phải chế nhạo bất kỳ lớp lib nào.
Ozeetee

3

Điều này có thể được thực hiện nếu bạn đang sử dụng Mockito2, với tính năng ủ mới hỗ trợ chế tạo các lớp & phương thức cuối cùng.

Những điểm chính cần lưu ý:
1. Tạo một tệp đơn giản với tên là org org.mockito.plugins.MockMaker, và đặt nó vào một thư mục có tên là mockito-extend. Thư mục này nên được tạo sẵn trên đường dẫn lớp.
2. Nội dung của tệp được tạo ở trên phải là một dòng như dưới đây:
mock-Maker-inline

Hai bước trên là bắt buộc để kích hoạt cơ chế mở rộng mockito và sử dụng tính năng chọn tham gia này.

Các lớp mẫu như sau: -

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println("Final class says Hello!!!");
    return "0";
}

}

Foo.java

public class Foo {

public String executeFinal(FinalClass finalClass){
    return finalClass.hello();
}

}

FooTest.java

public class FooTest {

@Test
public void testFinalClass(){
    // Instantiate the class under test.
    Foo foo = new Foo();

    // Instantiate the external dependency
    FinalClass realFinalClass = new FinalClass();

    // Create mock object for the final class. 
    FinalClass mockedFinalClass = mock(FinalClass.class);

    // Provide stub for mocked object.
    when(mockedFinalClass.hello()).thenReturn("1");

    // assert
    assertEquals("0", foo.executeFinal(realFinalClass));
    assertEquals("1", foo.executeFinal(mockedFinalClass));

}

}

Hy vọng nó giúp.

Toàn bộ bài viết hiện tại ở đây chế giễu-không thể thay đổi .


Bạn nên bao gồm câu trả lời ở đây và không liên kết đến một trang web bên ngoài. Nếu thủ tục dài, bạn có thể bao gồm một cái nhìn tổng quan.
rghome

vui lòng đảm bảo các chú thích bên dưới được sử dụng khi chế nhạo @RunWith (PowerMockRunner. class) @PrepareForTest ({AFinalClass. class})
vCillusion

1
@vCillusion - Ví dụ tôi đã trình bày chỉ sử dụng API Mockito2. Sử dụng tính năng chọn tham gia của Mockito2, người ta có thể mô phỏng trực tiếp các lớp cuối cùng mà không cần sử dụng Powermock.
ksl

2

Có cùng một vấn đề ở đây, chúng ta không thể chế nhạo một lớp cuối cùng với Mockito. Để chính xác, Mockito không thể chế nhạo / gián điệp sau:

  • lớp cuối cùng
  • lớp học ẩn danh
  • loại nguyên thủy

Nhưng sử dụng lớp bao bọc có vẻ là một cái giá lớn phải trả, vì vậy hãy lấy PowerMockito thay thế.


2

Tôi nghĩ bạn cần suy nghĩ nhiều hơn về nguyên tắc. Thay vào đó, lớp cuối cùng bạn sử dụng giao diện và giao diện giả của anh ấy.

Đối với điều này:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

thêm vào

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

và giả định giao diện của bạn:

 @Before
    fun setUp() {
        rainService= Mockito.mock(iRainOnTrees::class.java)

        `when`(rainService.startRain()).thenReturn(
            just(true).delay(3, TimeUnit.SECONDS)
        )

    }

1

Hãy nhìn vào JMockit . Nó có tài liệu phong phú với rất nhiều ví dụ. Ở đây bạn có một giải pháp ví dụ cho vấn đề của mình (để đơn giản hóa tôi đã thêm hàm tạo vào Seasonsđể thêm RainOnTreesphiên bản giả):

package jmockitexample;

import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class SeasonsTest {

    @Test
    public void shouldStartRain(@Mocked final RainOnTrees rain) {
        Seasons seasons = new Seasons(rain);

        seasons.findSeasonAndRain();

        new Verifications() {{
            rain.startRain();
        }};
    }

    public final class RainOnTrees {
        public void startRain() {
            // some code here
        }

    }

    public class Seasons {

        private final RainOnTrees rain;

        public Seasons(RainOnTrees rain) {
            this.rain = rain;
        }

        public void findSeasonAndRain() {
            rain.startRain();
        }

    }
}

1

Các giải pháp được cung cấp bởi RC và Luigi R. Viggiano cùng nhau có thể là ý tưởng tốt nhất.

Mặc dù Mockito không thể , theo thiết kế, mô phỏng các lớp cuối cùng, phương pháp ủy nhiệm là có thể . Điều này có lợi thế của nó:

  1. Bạn không bị buộc phải thay đổi lớp của mình thành không phải là chung kết nếu đó là những gì API của bạn dự định ở vị trí đầu tiên (các lớp cuối cùng có lợi ích của chúng ).
  2. Bạn đang kiểm tra khả năng trang trí xung quanh API của mình.

Trong trường hợp thử nghiệm của bạn, bạn cố tình chuyển tiếp các cuộc gọi đến hệ thống đang thử nghiệm. Do đó, theo thiết kế, trang trí của bạn không làm gì cả.

Do đó, bạn kiểm tra cũng có thể chứng minh rằng người dùng chỉ có thể trang trí API thay vì mở rộng nó.

Trên một lưu ý chủ quan hơn: Tôi thích giữ các khung ở mức tối thiểu, đó là lý do tại sao JUnit và Mockito thường là đủ cho tôi. Trên thực tế, việc hạn chế theo cách này đôi khi cũng buộc tôi phải tái cấu trúc cho tốt.


1

Nếu bạn cố chạy thử nghiệm đơn vị trong thư mục thử nghiệm , giải pháp hàng đầu là ổn. Chỉ cần làm theo nó thêm một phần mở rộng.

Nhưng nếu bạn muốn chạy nó với lớp liên quan đến Android như bối cảnh hoặc hoạt động trong thư mục androidtest , câu trả lời là dành cho bạn.


1

Thêm các phụ thuộc này để chạy mockito thành công:

testImcellenceation 'org.mockito: mockito-core: 2.24.5'
testImcellenceation "org.mockito: mockito-inline: 2.24.5"


0

Như những người khác đã tuyên bố, điều này sẽ không hoạt động với Mockito. Tôi sẽ đề nghị sử dụng sự phản chiếu để đặt các trường cụ thể trên đối tượng đang được sử dụng bởi mã đang được thử nghiệm. Nếu bạn thấy mình làm điều này rất nhiều, bạn có thể bọc chức năng này trong thư viện.

Như một bên, nếu bạn là một trong những lớp đánh dấu cuối cùng, ngừng làm điều đó. Tôi đã xem qua câu hỏi này bởi vì tôi đang làm việc với một API trong đó mọi thứ được đánh dấu là cuối cùng để ngăn chặn nhu cầu mở rộng hợp pháp của tôi và tôi ước rằng nhà phát triển đã không cho rằng tôi sẽ không bao giờ cần phải mở rộng lớp.


1
Các lớp API công khai nên được mở rộng. Hoàn toàn đồng ý. Tuy nhiên, trong một cơ sở mã riêng, finalnên được mặc định.
ErikE

0

Đối với chúng tôi, đó là vì chúng tôi đã loại trừ mockito-inline khỏi koin-test. Một mô-đun lớp thực sự cần điều này và vì lý do chỉ thất bại trong các bản dựng phát hành (bản dựng gỡ lỗi trong IDE đã hoạt động) :-P


0

Đối với lớp cuối cùng thêm bên dưới để giả và gọi tĩnh hoặc không tĩnh.

1- thêm cái này ở cấp lớp @SuppressStatucInitializationFor (value = {tên lớp với gói})
2- PowerMockito.mockStatic (classname. Class) sẽ giả định lớp
3- sau đó sử dụng câu lệnh khi của bạn để trả về đối tượng giả khi gọi phương thức của lớp này.

Thưởng thức


-5

Không thử cuối cùng, nhưng đối với riêng tư, sử dụng phản chiếu loại bỏ công cụ sửa đổi! đã kiểm tra thêm, nó không hoạt động cho cuối cùng.


đây không phải là trả lời câu hỏi được hỏi
Sanjit Kumar Mishra
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.