如何使用JUnit和Mockito为调用JNA原生库的ClassB编写单元测试
解决方案
你面临的核心问题是InterfaceA中的INSTANCE是静态final常量,默认Mockito无法直接替换,下面给出两种可行方案:
方案一:重构原有代码(优先推荐)
这是最符合可测试性设计的方案,仅需要对ClassB做少量修改,不需要引入额外测试依赖或复杂操作:
- 为ClassB增加构造函数,将InterfaceA作为依赖注入,保留无参构造函数兼容原有业务逻辑
重构后的代码如下:
package x.x.x; import com.sun.jna.Library; import com.sun.jna.Native; public class ClassB { public interface InterfaceA extends Library { InterfaceA INSTANCE = (InterfaceA) Native.load("ClassA.api", InterfaceA.class); int interfaceMethod1(); int interfaceMethod2(String string1); } private final InterfaceA interfaceA; // 原有无参构造函数,兼容现有业务代码调用逻辑 public ClassB() { this.interfaceA = InterfaceA.INSTANCE; } // 供测试用的构造函数,可传入mock实例 public ClassB(InterfaceA interfaceA) { this.interfaceA = interfaceA; } public boolean method1(){ int result = interfaceA.interfaceMethod1(); return result != 0; } public boolean method2(String string1){ int result = interfaceA.interfaceMethod2(string1); return result != 0; } }
对应的单元测试代码(JUnit 5 + Mockito):
package x.x.x; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; class ClassBTest { @Test void testMethod1() { // mock InterfaceA实例 InterfaceA mockInterfaceA = Mockito.mock(InterfaceA.class); // 定义mock方法返回值 when(mockInterfaceA.interfaceMethod1()).thenReturn(1); // 传入mock实例构造ClassB ClassB classB = new ClassB(mockInterfaceA); // 执行测试断言 assertTrue(classB.method1()); // 验证方法被调用 Mockito.verify(mockInterfaceA).interfaceMethod1(); } @Test void testMethod2() { InterfaceA mockInterfaceA = Mockito.mock(InterfaceA.class); when(mockInterfaceA.interfaceMethod2("test")).thenReturn(0); ClassB classB = new ClassB(mockInterfaceA); assertFalse(classB.method2("test")); } }
方案二:不修改原有代码,反射替换静态常量
如果无法修改原有业务代码,可以通过反射临时替换InterfaceA中的INSTANCE常量为mock对象,测试完成后恢复原值避免影响其他用例。
需要注意:JDK 16及以上版本需要添加JVM启动参数 --add-opens java.base/java.lang.reflect=ALL-UNNAMED 才能修改final字段。
测试代码示例:
package x.x.x; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; class ClassBTest { private Field instanceField; private InterfaceA originInstance; private InterfaceA mockInterfaceA; @BeforeEach void setUp() throws NoSuchFieldException, IllegalAccessException { // 构造mock实例 mockInterfaceA = Mockito.mock(InterfaceA.class); // 获取INSTANCE字段 instanceField = InterfaceA.class.getDeclaredField("INSTANCE"); instanceField.setAccessible(true); // 移除final修饰符 Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(instanceField, instanceField.getModifiers() & ~Modifier.FINAL); // 保存原有实例 originInstance = (InterfaceA) instanceField.get(null); // 替换为mock实例 instanceField.set(null, mockInterfaceA); } @AfterEach void tearDown() throws IllegalAccessException { // 恢复原有实例,避免影响其他测试用例 instanceField.set(null, originInstance); } @Test void testMethod1() { when(mockInterfaceA.interfaceMethod1()).thenReturn(1); ClassB classB = new ClassB(); assertTrue(classB.method1()); } @Test void testMethod2() { when(mockInterfaceA.interfaceMethod2("testStr")).thenReturn(0); ClassB classB = new ClassB(); assertFalse(classB.method2("testStr")); } }
补充:也可使用PowerMock框架通过字节码修改替换静态常量,本质和反射方案逻辑一致,但PowerMock对高版本JDK、JUnit 5的兼容性较差,非必要不推荐使用。
内容的提问来源于stack exchange,提问作者user344563
相关产品推荐
相关产品推荐

