使用 Mockito 模擬枚舉
1. 概述
枚舉是擴展[Enum](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/Enum.html)類別的特殊型別的類別。它由一組static final值組成,這使得它們不可更改。此外,我們使用枚舉來定義一個變數可以保存的一組預期值。透過這種方式,我們可以使程式碼更具可讀性並且不易出錯。
但是,在測試時,我們希望在某些情況下模擬枚舉值。在本教程中,我們將學習如何使用 Mockito 庫模擬枚舉,並討論這可能有用的情況。
2. 依賴設定
在我們深入之前,讓我們將mockito-core依賴項添加到我們的專案中:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.13.0</version>
<scope>test</scope>
</dependency>
需要注意的是,我們需要使用依賴版本 5.0.0 或更高版本才能模擬靜態方法。
對於較舊的 Mockito 版本,我們需要在pom.xml中添加額外的mockito-inline依賴項:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
我們不需要為更高版本添加mockito-inline因為它已合併在mockito-core中。
3. 設定範例
接下來,讓我們建立一個簡單的Direction枚舉,我們將在本教學中使用:
public enum Direction {
NORTH,
EAST,
SOUTH,
WEST;
}
此外,我們定義一個實用方法,該方法使用先前建立的枚舉並根據傳遞的值傳回描述:
public static String getDescription(Direction direction) {
return switch (direction) {
case NORTH -> "You're headed north.";
case EAST -> "You're headed east.";
case SOUTH -> "You're headed south.";
case WEST -> "You're headed west.";
default -> throw new IllegalArgumentException();
};
}
4. 模擬枚舉的解決方案
為了檢查應用程式的行為,我們可以在測試案例中使用已經定義的枚舉值。但是,在某些情況下,我們希望模擬枚舉並使用不存在的值。我們來談幾個。
我們需要這樣一個模擬的範例之一是確保我們將來添加到枚舉中的值不會導致意外的行為。另一個例子是目標是高百分比的程式碼覆蓋率。
現在,讓我們建立一個測試並模擬一個枚舉:
@Test
void givenMockedDirection_whenGetDescription_thenThrowException() {
try (MockedStatic<Direction> directionMock = Mockito.mockStatic(Direction.class)) {
Direction unsupported = Mockito.mock(Direction.class);
Mockito.doReturn(4).when(unsupported).ordinal();
directionMock.when(Direction::values)
.thenReturn(new Direction[] { Direction.NORTH, Direction.EAST, Direction.SOUTH,
Direction.WEST, unsupported });
assertThrows(IllegalArgumentException.class, () -> DirectionUtils.getDescription(unsupported));
}
}
在這裡,我們使用mockStatic()方法來模擬靜態方法呼叫的呼叫。此方法傳回Direction類型的MockedStatic物件。接下來,我們定義了一個新的模擬枚舉值,並指定了呼叫ordinal()方法時應該發生的情況。最後,我們將模擬值包含在values()方法傳回的結果中。
因此, getDescription()方法會針對unsupported值引發IllegalArgumentException 。
5. 不模擬枚舉的解決方案
接下來,讓我們研究如何實現相同的行為,但這次不模擬枚舉。
首先,讓我們在測試目錄中定義Direction枚舉並新增新的UNKNOWN值:
public enum Direction {
NORTH,
EAST,
SOUTH,
WEST,
UNKNOWN;
}
新建立的Direction枚舉應該與我們先前在來源目錄中定義的枚舉具有相同的路徑。
此外,由於我們在測試案例執行時使用與來源目錄中的完全限定名稱相同的完全限定名稱來定義枚舉,因此它們將使用測試來源目錄中提供的名稱。
另外,由於我們定義的枚舉與來源目錄中的枚舉具有相同的完全限定名稱,因此來源目錄下的Direction枚舉將被測試目錄中提供的枚舉重載。因此,測試引擎使用test目錄中提供的引擎。
讓我們測試一下getDescription()方法在傳遞UNKNOWN值時的行為:
@Test
void givenUnknownDirection_whenGetDescription_thenThrowException() {
assertThrows(IllegalArgumentException.class, () -> DirectionUtils.getDescription(Direction.UNKNOWN));
}
如預期的那樣,對getDescription()方法的呼叫會引發IllegalArgumentException異常。
使用這種方法的可能缺點之一是,現在我們的枚舉不屬於我們的原始程式碼,此外,它還會影響我們測試套件中的所有測試。
六、結論
在本文中,我們學習如何使用 Mockito 函式庫模擬枚舉。我們也研究了一些可能有用的場景。
總而言之,當我們想要測試引入新值時程式碼是否會按預期運行時,模擬枚舉會很有幫助。我們也學習如何透過重載現有枚舉而不是模擬其值來執行相同的操作。
與往常一樣,本文中使用的完整程式碼可以在 GitHub 上找到。