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 方法的組合)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class NonFinalClassWithFinalMethod {
final String finalMethod() {
return "something";
}
}
final class FinalClassWitnNonFinalMethod {
String nonFinalMethod() {
return "somthing";
}
}
final class FinalClassWithFinalMethod {
final String finalMethod() {
return "something";
}
}
|
對 final 修飾符視而不見的 Mock 測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package cc.unmi;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class FinalClassTest {
@Test
public void testNonFinalClassWithFinalMethod() {
NonFinalClassWithFinalMethod testMe = Mockito.mock(NonFinalClassWithFinalMethod.class);
when(testMe.finalMethod()).thenReturn("hello");
assertThat(testMe.finalMethod(), CoreMatchers.equalTo("hello"));
}
@Test
public void testFinalClassWithNonFinalMethod() {
FinalClassWitnNonFinalMethod testMe = Mockito.mock(FinalClassWitnNonFinalMethod.class);
when(testMe.nonFinalMethod()).thenReturn("hello");
assertThat(testMe.nonFinalMethod(), CoreMatchers.equalTo("hello"));
}
@Test
public void testFinalClassWithFinalMethod() {
FinalClassWithFinalMethod testMe = Mockito.mock(FinalClassWithFinalMethod.class);
when(testMe.finalMethod()).thenReturn("hello");
assertThat(testMe.finalMethod(), CoreMatchers.equalTo("hello"));
}
}
|
如果上面的待測試類沒有 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 待見的。
但是當再加上一個待測試類
1
2
3
4
5
|
class NonFinalClassWithNonFinalMethod {
String nonFinalMethod() {
return "something";
}
}
|
和測試用例
1
2
3
4
5
6
|
@Test
public void testNonFinalClassWithNonFinalMethod() {
NonFinalClassWithNonFinalMethod testMe = Mockito.mock(NonFinalClassWithNonFinalMethod.class);
when(testMe.nonFinalMethod()).thenReturn("hello");
assertThat(testMe.nonFinalMethod(), CoreMatchers.equalTo("hello"));
}
|
四個測試用例一起運行結果又不一樣了
此時單獨運行 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 類一樣的行為了
基於 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
模塊
1
2
3
4
5
6
|
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
|
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 的一個擴展。
本文鏈接 https://yanbin.blog/mockito-mock-final-class-final-method/, 來自 隔葉黃鶯 Yanbin Blog
[版權聲明] 本文采用 署名-非商業性使用-相同方式共享 2.5 通用 (CC BY-NC-SA 2.5) 進行許可。