Mockito 也能 Mock final 類和 final 方法了


https://yanbin.blog/mockito-mock-final-class-final-method/

 

以實際 Java 項目中的單元測試 Mock 框架基本是 Mockito 2 了,因為它有一個十分流暢的 API。Mockito  2也為 JUnit 5 配上了 MockitoExtension, 所以 JUnit 5 下使用 Mockito 2 的關節也打通了。但在我們享受 Mockito 2 便利的同時,與 JMockit 相比局限性就很明顯,因為 Mockito 2 是通過創建匿名子類來進行 Mock 的,所以任何子類對父類無法突破的方面它都存在。譬如,final 類, final 方法, 私有方法, 靜態方法, 構造函數都是無法通過子類型進行重寫的。所以除非特別需要,在 Mockito 2 無法勝任時都求助於 JMockit,JMockit 借助於 javaagent 取得了 JVM 的高控制權才得已為所欲為。

當 Mockito 來到了 2.1.0 版本,它也覺得不能對以上所有的限制置若罔聞, 首先帶給我們的突破是它也可以 Mock final 類和 final 方法,雖然仍處於孵化器中,但畢竟是應用在單元測試中,能用就很不錯了,只要以后不被拿走就行。這是官方對它的介紹 Mock the unmockable: opt-in mocking of final classes/methods

下面我親自操作一遍,並給出更全方位的測試樣例

待測試類(final 類與 final 方法的組合)

 

 

對 final 修飾符視而不見的 Mock 測試

 

 

如果上面的待測試類沒有 final 類和 final 方法,上面三個測試用例都能成功通過。然而基於待測試類存在 final 修飾符的事實,以上測試用例將全部失敗,它們的錯誤信息將分別是

testNonFinalClassWithFinalMethod():
org.mockito.exceptions.misusing.MissingMethodInvocationException:
    when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
    Those methods *cannot* be stubbed/verified.
    Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
testFinalClassWithNonFinalMethod():
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class cc.unmi.FinalClassWitnNonFinalMethod
Mockito cannot mock/spy because :
  - final class
testFinalClassWithFinalMethod:
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class cc.unmi.FinalClassWitnNonFinalMethod
Mockito cannot mock/spy because :
  - final class

就是說正常情況下 final class, final method 等是不受 Mockito 待見的。

但是當再加上一個待測試類

 

 

和測試用例

 

 

四個測試用例一起運行結果又不一樣了

此時單獨運行 testNonFinalClassWithFinalMethod 還是提示與之前一樣的錯誤

org.mockito.exceptions.misusing.MissingMethodInvocationException:
    when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
    Those methods *cannot* be stubbed/verified.
    Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

添加配置文件達成對 final 類 final 方法的 Mock

那么 Mockito 聲稱能 Mock final 類和 final 方法的角決之道在哪里呢?一個額外的配置文件,對於 Maven 標准布局的項目,創建這個文件

src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

如果是其他項目的話,應該只需保證在 classpath 下有文件 mockito-extensions/org.mockito.plugins.MockMaker

該文件的內容是

mock-maker-inline

對,它就像是 SPI 模式的類似 META-INF/services/org.junit.platform.engine.TestEngine 文件。

有了 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 文件,及內容 mock-maker-inline 后,再次運行上面的測試用例

全綠,說明 Mockito 能對 final 類和 final 方法進行 Mock 了。這已經是 Mockito 的一大進步,先別對 private, static 有所奢欲。

MockMaker 讓 Mockito 發生了什么

我們可以通過以下幾張圖來了解在 src/test/resources/mockio-extensions/org.mockito.plugins.MockMaker (mock-maker-inline) 前后發生了什么

前前,考查 Mock 的對象

 

Mock 非 final 類創建子類

不能 Mock final 類

后后,再次考查 Mock 的對象

終於 final 類也可以 Mock 了, 不能生成子類,像是直接生成它的實例,也無可非

可是對於非 final 類型也不再生成子類了,和 mock final 類一樣的行為了

基於 Mockito.mock(clazz) 直接創建實例這一事實,原理上我們可以使用 Mockito 來 Mock 構造函數的。

前前后后,針對接口和抽象類

為抽象類創建子類型為接口也是創建子類型

對於接口和抽象類的 Mock 行為始終沒變,總是必須創建它們的子類型。

為什么是 mock-maker-inline

既然前面猜測 src/resources/mockito-extensions/org.mockito.plugins.MockMaker 像是 SPI 模式,那么它大約就真的是了,的確是。其中可以寫 mock-maker-inline, 必定也可以寫上別的什么,自然要查找下 MockMaker 接口有哪些實現

有三個實現可選,mock-maker-inline 毫無疑問對應的就是 InlineByteBuddyMockMaker, 默認的實現方式也很容易想像得出來就是 SubclassByteBuddyMockMaker, 猜測配置為 mock-maker-subclass, 經驗證不對。如果要顯式的指定采用 SubclassByteBuddyMockMaker, 那么 src/resource/mockto-extensions/org.mockito.plugins.MockMaker 文件的內容是

org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker

而要用 ByteBuddyMockMaker 的話,該文件內容是

org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker

Mockito 目前提供的所有三個 MockMaker 實現,只有 InlineByteBuddyMockMaker 能用來 Mock final 類和 final 方法,mock-maker-inline 也可以配置為完整的類名

org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker

細看 org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker 類能發現它其中使用到了 JDK 的 Instrumentation API 和 ByteBuddyAgent 在生成實例時對方法實現進行了替換。

PowerMockito(PowerMock) 是什么來頭

根據 Mockito 預留下來的 MockMaker 的實現,實際上給了第三方實現進行擴展對靜態或私有方法的 Mock 了,這也就是讓 Mockito 讓 PowerMock 有可乘之機。因此,如果要 Mock 靜態或私有方法的話,除 JMockit 外還有另一選擇,那就是 PowerMock,而且還能繼續使用 Mockito 的 API。

有了一個基本的推測后,順道看一下 PowerMock 帶來了什么 MockMaker 實現,在 pom.xml 加入當前最新的 powermock-api-mockito 模塊

 

 

powermock-api-mockito2 在Maven 中央倉庫正式版當前為 1.7.4, 所帶的 Mockito 還是  2.8.9, 當前 Mockito 版本是 2.18.3, 落后也並不大。在 Maven 中央倉庫還有 2.0.0-beta.5 的 powermock-api-mockito2, 所帶的 Mockito 2 是 2.10.0 版本。

powermock-api-mockito2 為 Mockito 2 提供了 MockMaker 的一個第三方實現是

org.powermock.api.mockito.mockmaker.PowerMockMaker

也正是它完善了 Mockito 2 對靜態方法,私有方法,以及構造函數的 Mock 的,所以說本質上 powermock-api-mockito2 就是 Mockito 2 的一個擴展。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM