Java單元測試實踐-00.目錄(9萬多字文檔+700多測試示例)
https://blog.csdn.net/a82514921/article/details/107969340
1. Mockito與PowerMock的其他功能
1.1. 擷取Mock對象詳細資訊
Mockito提供了用于擷取Mock對象詳細資訊的API,可參考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#mocking_details 。
1.1.1. 判斷指定對象是否為Mock/Spy對象
使用Mockito.mockingDetails()方法,可以擷取指定對象對應的MockingDetails執行個體,可以獲得Mock的相關資訊。
MockingDetails.isMock()、isSpy()方法分别可以判斷指定的對象是否為Mock對象或Spy對象。
使用MockingDetails.isMock()、isSpy()方法可以準确地判斷指定的對象是Mock對象、Spy對象還是原始對象,使用示例如下,可參考示例TestSpringMockDetailIs1類。
assertFalse(Mockito.mockingDetails(new TestServiceA1Impl()).isMock());
assertFalse(Mockito.mockingDetails(new TestServiceA1Impl()).isSpy());
assertTrue(Mockito.mockingDetails(Mockito.mock(TestServiceA1.class)).isMock());
assertTrue(Mockito.mockingDetails(Mockito.spy(TestServiceA1.class)).isSpy());
1.1.1.1. 将對象中的成員變量替換為Mock/Spy對象公共方法
在替換成員變量時,為了防止覆寫原有Stub操作,可以使用MockingDetails.isMock()、isSpy()方法來準确地判斷指定成員變量是否已經是Mock/Spy對象。
使用MockingDetails.isMock()、isSpy()方法判斷對象是否為Mock/Spy對象的功能,将替換成員變量的操作整理為公共方法,能夠實作将指定對象的指定類型成員變量替換為Mock/Spy對象,可以簡化操作,并避免将現有Mock/Spy對象的Stub操作覆寫。
在示例代碼中,提供了将成員變量替換為Mock/Spy對象,且可以避免覆寫原有Stub的簡化公共方法。可使用示例TestReplaceUtil類的replaceMockMember、replaceSpyMember方法。
示例如下,傳回的對象為testServiceB1對象中的TestServiceA1類的Mock/Spy對象:
TestServiceA1 testServiceA1InB1 = TestReplaceUtil.replaceMockMember(testServiceB1, TestServiceA1.class);
TestServiceA1 testServiceA1InB1 = TestReplaceUtil.replaceSpyMember(testServiceB1, TestServiceA1.class);
以上公共方法的使用可參考示例TestSpMockMemberOfMCommRun、TestSpMockMemberOfMCommMock、TestSpSpyMemberOfMCommRun、TestSpSpyMemberOfMCommSpy類。
1.1.2. 擷取Mock對象原始類型及預設Answer等詳細資訊
使用MockingDetails.getMockCreationSettings()方法,可以擷取對應的MockCreationSettings執行個體,可以獲得Mock在建立時的設定。
- 擷取Mock對象原始類型
使用MockCreationSettings.getTypeToMock()方法可以擷取Mock對象原始類型,使用示例如下,可參考示例TestSpringMockDetailMock1類。
assertEquals(RuntimeException.class,
Mockito.mockingDetails(Mockito.mock(RuntimeException.class)).getMockCreationSettings().getTypeToMock());
- 擷取預設Answer
使用MockCreationSettings.getDefaultAnswer()方法可以擷取Mock對象的預設Answer,使用示例如下,可參考示例TestSpringMockDetailMock1類。
assertSame(Mockito.RETURNS_DEFAULTS,
Mockito.mockingDetails(Mockito.mock(RuntimeException.class)).getMockCreationSettings().getDefaultAnswer());
擷取Spy對象的以上資訊可參考示例TestSpringMockDetailSpy1類。
1.1.3. 擷取Mock對象的Stub設定及方法調用情況
- 擷取Mock對象的Stub設定
使用MockingDetails.getStubbings()方法,可以擷取指定Mock對象的Stub設定,類型為Collection<Stubbing>,Mock對象初始的Stub設定數量為0,每設定一個Stub條件,數量加1。
Stubbing對象的toString()方法會傳回Stub的詳細資訊。設定以下Stub條件後,通過MockingDetails.getStubbings()方法擷取Stubbing對象并列印,如下所示。可參考示例TestSpringMockDetailMock2類。
Mockito.when(testServiceA1Mock.test1(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
Mockito.doAnswer(invocation -> null).when(testServiceA1Mock).test2(Mockito.any(StringBuilder.class));
Mockito.when(testServiceA1Mock.test3(Mockito.anyString())).thenThrow(new RuntimeException(TestConstants.MOCKED));
testServiceA1.test1(<any string>); stubbed with: [Returns: MOCKED]
testServiceA1.test2(
<any java.lang.StringBuilder>
); stubbed with: [adrninistrator.test.testmock.detail.TestSpringMockDetailMock2$$Lambda$1/[email protected]]
testServiceA1.test3(<any string>); stubbed with: [[email protected]]
- 擷取Mock對象的方法調用情況
使用MockingDetails.getInvocations()方法,可以擷取指定Mock對象的方法調用情況,類型為Collection<Invocation>,初始數量為0,每調用一次方法,數量加1。
Invocation對象的toString()方法會傳回Invocation的詳細資訊。調用以下方法後,通過MockingDetails.getInvocations()方法擷取Invocation對象并列印,如下所示:
testServiceA1Mock.test1(TestConstants.FLAG1);
testServiceA1Mock.test2(new StringBuilder().append(TestConstants.FLAG2));
testServiceA1.test1("flag1");
testServiceA1.test2(flag2);
MockingDetails.printInvocations(),以友好的方式展示指定Mock對象已發生的的調用資訊。此外,該方法還會列印Stub資訊,包括未使用的Stub。該方法傳回類型為String。以上Mock對象的MockingDetails.printInvocations()傳回結果如下所示。可參考示例TestSpringMockDetailMock2類。
[Mockito] Interactions of: Mock for TestServiceA1, hashCode: 800722348
1. testServiceA1.test1("flag1");
-> at adrninistrator.test.testmock.detail.TestSpringMockDetailMock2.test(TestSpringMockDetailMock2.java:51)
- stubbed -> at adrninistrator.test.testmock.detail.TestSpringMockDetailMock2.init(TestSpringMockDetailMock2.java:32)
2. testServiceA1.test2(flag2);
-> at adrninistrator.test.testmock.detail.TestSpringMockDetailMock2.test(TestSpringMockDetailMock2.java:54)
- stubbed -> at adrninistrator.test.testmock.detail.TestSpringMockDetailMock2.init(TestSpringMockDetailMock2.java:35)
[Mockito] Unused stubbings of: Mock for TestServiceA1, hashCode: 800722348
1. testServiceA1.test1("");
- stubbed -> at adrninistrator.test.testmock.detail.TestSpringMockDetailMock2.init(TestSpringMockDetailMock2.java:32)
2. testServiceA1.test2(null);
- stubbed -> at adrninistrator.test.testmock.detail.TestSpringMockDetailMock2.init(TestSpringMockDetailMock2.java:35)
3. testServiceA1.test3("");
- stubbed -> at adrninistrator.test.testmock.detail.TestSpringMockDetailMock2.init(TestSpringMockDetailMock2.java:38)
擷取Spy對象的以上資訊可參考示例TestSpringMockDetailSpy2類。
1.2. 使用@InjectMocks注解實作Mock/Spy對象的自動注入
使用@InjectMocks注解可以實作Mock/Spy對象的自動注入。例如使用@InjectMocks注解指定C類的執行個體,再使用@Mock/@Spy注解指定C類中包含的成員變量A、B類的執行個體,可以實作将A、B類的Mock/Spy對象自動注入至C類執行個體中。示例如下:
@InjectMocks
private C c;
@Spy
private B b = new B();
@Mock
private A a;
@InjectMocks注解需要指定實作類對象,不能指定接口對象,否則會出現類似以下異常:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate @InjectMocks field named 'testServiceC1'! Cause: the type 'TestServiceC1' is an interface.
You haven't provided the instance at field declaration so I tried to construct the instance.
使用@InjectMocks注解指定的生成的對象為原始類的對象,不是Mock/Spy對象。以上可參考示例TestInjectMocks1、TestInjectMocks2類。
當使用@InjectMocks注解指定某類的執行個體時,對于未使用@Mock/@Spy注解指定的成員變量,不會被自動注入,即值為null。可參考示例TestInjectMocks3類。
1.2.1. 不推薦使用@InjectMocks注解
不推薦使用@InjectMocks注解實作Mock/Spy對象的自動注入,當需要使用與@InjectMocks注解類似的功能時,可以使用Whitebox.setInternalState()等方法通過反射的方式對成員變量進行替換。不推薦的原因如下:
- 使用不友善
對于@InjectMocks注解指定的類的執行個體,需要使用@Mock/@Spy注解指定每個成員變量,未指定的成員變量值會為null;
- 執行成員變量的真實方法不友善
對于@InjectMocks注解指定的類的執行個體,注入的成員變量為Mock/Spy對象,當需要執行成員變量的真實方法時不友善。
1.3. MockitoAnnotations.initMocks()方法
在測試類級别使用@PrepareForTest注解時,若未執行MockitoAnnotations.initMocks()方法,測試代碼執行時使用了@InjectMocks、@Mock注解的對象為null,使用@Spy注解的對象不是null;在測試代碼中執行MockitoAnnotations.initMocks()方法後,@InjectMocks、@Mock注解對應的對象會變為非null。若未在測試類中使用@InjectMocks、@Mock注解,則不需要調用MockitoAnnotations.initMocks()方法。
示例如下,可參考示例TestInitMocksNoPrepare、TestInitMocksWithPrepare1、TestInitMocksWithPrepare2類。
// 在測試類級别指定
@PrepareForTest({})
@InjectMocks
private TestServiceC1Impl testServiceC1Impl;
@Mock
private TestServiceA1 testServiceA1Mock;
@Before
public void init() {
assertNull(testServiceC1Impl);
assertNull(testServiceA1Mock);
MockitoAnnotations.initMocks(this);
assertNotNull(testServiceC1Impl);
assertNotNull(testServiceA1Mock);
}
1.4. Mockito.reset()方法
Mockito.reset()方法可以對Mock進行重置。參考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#resetting_mocks 。良好的單元測試代碼幾乎不需要使用該功能。通常,不需要對Mock進行重置,隻需要為每個測試方法建立新的Mock即可。
1.5. Mockito的Stub操作支援的對象
Mockito的Stub操作(Mockito.when()、Mockito.do…().when()等方法),隻支援Mock/Spy對象,不支援原始對象。可參考示例TestStubSupport類。
1.6. 擷取私有成員變量
當需要擷取私有成員變量時,可使用Whitebox.getInternalState()方法,支援指定變量類型(對應類型的變量隻存在一個)或變量名稱擷取。
也可以使用PowerMockito.field().get()方法擷取私有成員變量,但不如Whitebox.getInternalState()方法簡單。
示例如下,可參考示例TestGetPrivateField類。
@Autowired
private TestPublicNonVoidService1 testPublicNonVoidService1;
TestTableMapper testTableMapper1 = (TestTableMapper) PowerMockito.field(TestPublicNonVoidService1Impl.class,
"testTableMapper").get(testPublicNonVoidService1);
TestTableMapper testTableMapper2 = Whitebox.getInternalState(testPublicNonVoidService1, TestTableMapper.class);
TestTableMapper testTableMapper3 = Whitebox.getInternalState(testPublicNonVoidService1, "testTableMapper");
1.6.1. 檢查對象字段值是否等于預期值的簡化方法
以下方法使用了Whitebox.getInternalState()方法。
示例代碼中提供了檢查對象字段值是否等于預期值的簡化方法:TestCommonUtil.checkObjectValue(),支援對字元串、int、long、BigDecimal、Date等類型進行比較,可以避免對需要檢查的對象的每個字段值逐個進行assertEquals等判斷,通過數組指定需要比較的字段名稱及預期值,可以減少代碼量。
以上方法參數1為需要檢查的對象,參數2為Object數組,内容為需要檢查的資訊,元素應為偶數個,下标為奇數的元素為key,下标為偶數的元素為value,使用示例如下:
Date date = new Date();
TestTable2 testTable2 = new TestTable2();
testTable2.setId(TestConstants.FLAG1);
testTable2.setChar1(TestConstants.FLAG1);
testTable2.setChar2(TestConstants.FLAG1);
testTable2.setText2(TestConstants.FLAG1);
testTable2.setInt1(1);
testTable2.setInt2(2);
testTable2.setDecimal1(100L);
testTable2.setDecimal2(BigDecimal.TEN);
testTable2.setDatetime1(date);
testTable2.setDatetime2(date);
testTable2.setTimestamp1(date);
testTable2.setTimestamp2(date);
TestCommonUtil.checkObjectValue(testTable2, new Object[]{"id", TestConstants.FLAG1, "char1", TestConstants.FLAG1,
"char2", TestConstants.FLAG1, "text2", TestConstants.FLAG1, "int1", 1, "int2", 2, "decimal1", 100L, "decimal2", BigDecimal.TEN,
"datetime1", date, "datetime2", date, "timestamp1", date, "timestamp2", date});
可參考示例TestCheckObjectValue類。
1.7. 替換私有成員變量
當需要替換私有成員變量時,可使用Whitebox.setInternalState()方法,支援指定變量類型,或變量名稱對變量進行替換,或直接指定變量進行替換(不需要指定變量類型或名稱,需要滿足對應類型的變量隻存在一個);當直接指定變量進行替換時,支援一次替換多個變量。
也可以使用PowerMockito.field().set()方法替換私有成員變量,但不如Whitebox.setInternalState()方法簡單。
示例如下,可參考示例TestSetPrivateField類。
TestNonStatic2 testNonStatic2 = new TestNonStatic2();
PowerMockito.field(TestNonStatic2.class, "flag").set(testNonStatic2, TestConstants.FLAG1);
Whitebox.setInternalState(testNonStatic2, String.class, TestConstants.FLAG2);
Whitebox.setInternalState(testNonStatic2, TestConstants.FLAG3);
Whitebox.setInternalState(testNonStatic1, "str1", TestConstants.FLAG1);
1.8. 建立構造函數為私有的類的執行個體
對于構造函數為私有的類,當需要建立執行個體時,可以調用Whitebox.newInstance()或Whitebox.invokeConstructor()方法,示例如下:
TestPrivateConstructor2 testPrivateConstructor2 = Whitebox.newInstance(TestPrivateConstructor2.class);
TestPrivateConstructor2 testPrivateConstructor2 = Whitebox.invokeConstructor(TestPrivateConstructor2.class);
以上方法的差別在于,Whitebox.newInstance()方法不會調用構造函數,Whitebox.invokeConstructor()方法會調用構造函數。可參考示例TestNewPrivateConstructor1、TestNewPrivateConstructor2類。
1.9. 執行私有方法
當需要執行私有方法時,可使用Whitebox.invokeMethod()方法,支援執行靜态私有方法及非靜态私有方法。
也可以使用PowerMockito.method().invoke()方法執行私有方法,但不如Whitebox.invokeMethod()方法簡單。
示例如下,可參考示例TestInvokePrivateMethod類。
@Autowired
private TestPrivateNonVoidService1 testPrivateNonVoidService1;
String str = Whitebox.invokeMethod(TestStaticPrivateNonVoid1.class, TestStaticPrivateNonVoid1.NAME_TEST3);
String str = Whitebox.invokeMethod(testPrivateNonVoidService1, TestPrivateNonVoidService1Impl.NAME_TEST1, "");
String str = (String) PowerMockito.method(TestPrivateNonVoidService1Impl.class, TestPrivateNonVoidService1Impl
.NAME_TEST1).invoke(testPrivateNonVoidService1, "");
2. 使用Mock禁止Spring定時任務
在執行單元測試用例時,若滿足了定時任務的執行時間要求,會使定時任務執行,可能對測試造成幹擾,可以使用Mock禁止定時任務。
以下以使用Mock禁止Spring定時任務為例,若使用其他的定時任務架構,也可以參考。
Spring定時任務的初始化在ScheduledTaskRegistrar.scheduleTasks()方法中完成,在該方法中會分别完成triggerTasks、cronTasks、fixedRateTasks、fixedDelayTasks等定時任務的初始化。
使用Mock禁止Spring定時任務,需要使用@PrepareForTest注解指定ScheduledTaskRegistrar類,在@BeforeClass注解對應的方法中禁止ScheduledTaskRegistrar.scheduleTasks()方法,如下所示:
@PrepareForTest({ScheduledTaskRegistrar.class})
@BeforeClass
public static void beforeClass() {
PowerMockito.suppress(PowerMockito.method(ScheduledTaskRegistrar.class, "scheduleTasks"));
}
可參考示例TestSpringTaskEnabled類,若不禁止Spring定時任務,在單元測試執行期間會執行Spring定時任務。
可參考示例TestSpringTaskDisabled類,使用以上方法禁止Spring定時任務後,在單元測試執行期間不會執行Spring定時任務。