能否为测试重定义第三方类内部的Java Enum枚举?
给第三方内部枚举加测试用枚举值的解决办法
因为要改的枚举是第三方类里的内部枚举,没法直接改源码,咱们可以用字节码动态修改的方式给它加个新枚举常量,就能触发switch的default分支了。下面是用ByteBuddy实现的具体步骤:
1. 先加依赖
测试环境里引入ByteBuddy的包(以Maven为例):
<dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.14.11</version> <scope>test</scope> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.14.11</version> <scope>test</scope> </dependency>
2. 动态修改枚举类
写测试代码的时候,先通过ByteBuddy给AClass.AnEnum加新枚举常量,再让你的方法返回这个新值:
import net.bytebuddy.ByteBuddy; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.matcher.ElementMatchers; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; public class YourTestClass { @BeforeAll static void setup() { // 启动ByteBuddy代理 ByteBuddyAgent.install(); // 给AClass.AnEnum加个新枚举常量C new ByteBuddy() .redefine(AClass.AnEnum.class) .defineField("C", AClass.AnEnum.class, java.lang.reflect.Modifier.PUBLIC | java.lang.reflect.Modifier.STATIC | java.lang.reflect.Modifier.FINAL) .method(ElementMatchers.named("values")) .intercept(FieldAccessor.ofField("$VALUES").andThen(FieldAccessor.onField("$VALUES").setsValue( // 把新枚举实例加到原有的常量数组里 addNewEnumValue(AClass.AnEnum.class.getEnumConstants(), "C", 2) ))) .make() .load(AClass.AnEnum.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); } private static <T extends Enum<T>> T[] addNewEnumValue(T[] originalValues, String name, int ordinal) { @SuppressWarnings("unchecked") T[] newValues = (T[]) java.lang.reflect.Array.newInstance(originalValues.getClass().getComponentType(), originalValues.length + 1); System.arraycopy(originalValues, 0, newValues, 0, originalValues.length); try { // 用反射创建新的枚举实例(枚举都有String和int参数的私有构造器) java.lang.reflect.Constructor<T> constructor = originalValues.getClass().getComponentType().getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); newValues[originalValues.length] = constructor.newInstance(name, ordinal); } catch (Exception e) { throw new RuntimeException(e); } return newValues; } @Test void testDefaultBranch() { // 让aMethodThatReturnsAnEnumValueFromContext返回新加的枚举值C when(yourInstance.aMethodThatReturnsAnEnumValueFromContext()).thenReturn(AClass.AnEnum.valueOf("C")); // 验证switch的default分支返回null assertNull(yourInstance.someMethod()); } }
3. 关键点说明
- 用ByteBuddy重定义枚举类,加新的静态字段
C,同时修改values()方法的返回值,把新枚举实例塞进常量数组。 - 反射创建枚举实例时,用的是枚举默认的私有构造器(每个枚举都有
String name, int ordinal这个构造器)。 - 必须先启动ByteBuddy代理,才能让类被重新加载生效。
注意事项
- 这种字节码修改的方式只适合测试用,绝对不能用到生产代码里。
- 如果第三方类是被Bootstrap类加载器加载的,可能得调整类加载策略才能生效。
- 有些测试框架或者构建工具可能需要额外配置,才能支持类重加载。
内容的提问来源于stack exchange,提问作者Christophe Broeckx
相关产品推荐
相关产品推荐

