You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java单元测试中模拟枚举以验证异常抛出?

问题描述

我有如下Factory类:

public class EmployeeFactory {

    public static Employee createEmployee(Employee employee) {

        switch (employee) {
            case DOCTOR:
                return new Doctor();
            case NURSE:
                return new Nurse();
        }

       throw new EmployeeException("Invalid Employee!");
    }

}

枚举类:

public enum Employee {

    DOCTOR("Doctor"),
    NURSE("Nurse");

    private final String description;

    Employee(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}

我想要创建一个模拟枚举(例如TEACHER)作为工厂方法的无效输入,以断言会抛出对应的exception。请问该如何实现?

当前测试代码:

@Test
void shouldThrowException(){

    assertThrows(EmployeeException.class,
            () -> {
                // arrange
                //todo - how to?
                Employee invalidEmployee = mock(Employee.class);
                when(invalidEmployee.getDescription()).thenReturn("Teacher");

                // act
                EmployeeFactory.createEmployee(invalidEmployee);
            });
}
解决方案

首先明确:Java枚举类型无法被正常Mock,因为枚举类构造方法私有,且每个实例都是单例,Mockito无法创建有效的枚举Mock实例——强行Mock会导致switch语句无法匹配(枚举switch是基于实例身份匹配,而非方法返回值)。

以下是两种可行的测试方案:

方案一:通过反射创建未定义的枚举实例

利用反射绕过枚举的限制,生成一个不属于现有常量的枚举实例,仅建议用于测试场景:

@Test
void shouldThrowExceptionWhenInvalidEmployee() throws Exception {
    // 获取枚举的私有构造方法
    Constructor<Employee> constructor = Employee.class.getDeclaredConstructor(String.class);
    constructor.setAccessible(true);
    
    // 创建自定义的"TEACHER"枚举实例
    Employee invalidEmployee = constructor.newInstance("Teacher");
    
    // 断言抛出预期异常
    assertThrows(EmployeeException.class, 
                () -> EmployeeFactory.createEmployee(invalidEmployee));
}

注意:该方式依赖反射机制,可能存在JDK版本兼容性问题,不符合枚举设计初衷。

方案二:调整Factory方法参数类型(更推荐)

如果允许修改代码结构,建议将Factory方法的参数改为枚举的序号或自定义标识,这样更易模拟无效输入:

// 修改后的EmployeeFactory类
public class EmployeeFactory {
    // 新增接收序号的方法,用于测试无效输入
    public static Employee createEmployee(int employeeOrdinal) {
        Employee[] values = Employee.values();
        if (employeeOrdinal < 0 || employeeOrdinal >= values.length) {
            throw new EmployeeException("Invalid Employee!");
        }
        return createEmployee(values[employeeOrdinal]);
    }

    // 保留原方法供正常业务调用
    public static Employee createEmployee(Employee employee) {
        switch (employee) {
            case DOCTOR:
                return new Doctor();
            case NURSE:
                return new Nurse();
            default:
                throw new EmployeeException("Invalid Employee!");
        }
    }
}

// 对应的测试代码
@Test
void shouldThrowExceptionWhenInvalidOrdinal() {
    // 传入超出枚举范围的序号(现有枚举只有0、1两个值)
    assertThrows(EmployeeException.class, 
                () -> EmployeeFactory.createEmployee(2));
}

这种方式符合Java设计规范,测试逻辑简单可靠。

原测试代码无效的原因

原代码中Mock的Employee实例,在switch语句中无法匹配任何case分支,理论上应抛出异常,但Mockito对枚举的Mock存在限制,可能导致不可预期的行为(比如抛出MockitoException而非预期的EmployeeException),因此不推荐Mock枚举来测试。

内容的提问来源于stack exchange,提问作者java12399900

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 09:16:35