如何在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
相关产品推荐
相关产品推荐

