1. 概述

枚举(enum)是一种特殊的类,它继承自 Enum 类。枚举由一组静态最终(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,需额外添加 mockito-inline 依赖:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>5.2.0</version>
    <scope>test</scope>
</dependency>

新版 Mockito 已将 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));
    }
}

关键步骤:

  1. 使用 mockStatic() 模拟静态方法调用
  2. 创建模拟枚举值并指定 ordinal() 返回值
  3. 修改 values() 方法返回结果,包含模拟值

最终 getDescription() 方法对模拟值抛出 IllegalArgumentException

5. 不模拟枚举的替代方案

另一种实现方式是重载枚举定义。在测试目录创建同名枚举并添加新值:

public enum Direction {
    NORTH,
    EAST,
    SOUTH,
    WEST,
    UNKNOWN;
}

⚠️ 注意:测试枚举必须与源码枚举的完全限定名相同

测试执行时,JVM 会优先使用测试目录中的枚举定义。测试用例:

@Test
void givenUnknownDirection_whenGetDescription_thenThrowException() {
    assertThrows(IllegalArgumentException.class, () -> DirectionUtils.getDescription(Direction.UNKNOWN));
}

此方案的缺点

  • 测试目录中存在非源码枚举
  • 影响整个测试套件的所有用例

6. 总结

本文介绍了使用 Mockito 模拟枚举的两种技术:

  1. 动态模拟:通过 Mockito.mockStatic() 修改枚举行为
  2. 静态重载:在测试目录重定义枚举

模拟枚举特别适合验证代码对未来新增枚举值的健壮性。两种方案各有优劣,动态模拟更灵活,静态重载更简单但影响范围大。

完整示例代码见 GitHub 仓库


原始标题:Mocking an Enum Using Mockito | Baeldung