如何Mock Java反射实现Hive自定义UDF的单元测试?
解决Hive自定义UDF反射调用的单元测试Mock空指针问题
我需要开发一个通过Java反射调用外部类的Hive自定义UDF,基础实现已完成,但编写单元测试时Mock反射API遇到NullPointerException,恳请指导正确的Mock方式解决问题。
相关代码示例
ReverseString.java
public class ReverseString extends GenericUDF { private StringObjectInspector input; Class<?> c = null; Object ob = null; @Override public ObjectInspector initialize(ObjectInspector[] arg0) throws UDFArgumentException { // create an ObjectInspector for the input ObjectInspector input = arg0[0]; // check to make sure the input is a string if (!(input instanceof StringObjectInspector)) { throw new UDFArgumentException("input must be a string"); } this.input = (StringObjectInspector) input; System.out.println("Success. Input formatted correctly"); return init(arg0); } public ObjectInspector init(ObjectInspector[] arg0) throws UDFArgumentException { try { c = Class.forName("com.hive.inherit.DummyUdf"); ob = c.getConstructor().newInstance(); Method method = c.getMethod("print",String.class); String res = (String) method.invoke(ob,"TEst"); System.out.println("RES: "+res); } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InvocationTargetException | InstantiationException e) { e.printStackTrace(); } return PrimitiveObjectInspectorFactory.javaStringObjectInspector; } @Override public Object evaluate(DeferredObject[] arg0) throws HiveException { if (input == null || arg0.length != 1 || arg0[0].get() == null) { return null; } String forwards = input.getPrimitiveJavaObject(arg0[0].get()); System.out.println("forwards:"+forwards); return reverse(forwards); } public Object reverse(String in) { Object res = null ; try { if (this.c != null && this.ob != null) { Method method = this.c.getMethod("reverse", String.class); res = (String) method.invoke(this.ob, in); }else{ c = Class.forName("com.hive.inherit.DummyUdf"); ob = c.getConstructor().newInstance(); Method method = c.getMethod("reverse", String.class); res = method.invoke(ob, in); } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException | InstantiationException e) { e.printStackTrace(); } return res; } @Override public String getDisplayString(String[] strings) { return null; } }
DummyUdf.java
package com.hive.inherit; public class DummyUdf { public DummyUdf(){ System.out.println("DummyUdf"); } public String print(String str){ System.out.println("DummyUdf-str:"+str); return str; } public String reverse(String in) { int l = in.length(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < l; i++) { sb.append(in.charAt(l - i - 1)); } return sb.toString(); } }
ReverseStringTest.java
@RunWith(MockitoJUnitRunner.class) public class ReverseStringTest { @Test public void testSimpleString() throws HiveException { //ReverseString r = Mockito.spy(new ReverseString()); ReverseString r = mock(ReverseString.class); ObjectInspector input = PrimitiveObjectInspectorFactory.javaStringObjectInspector; when(r.init(Mockito.any())).thenReturn(input); JavaStringObjectInspector resultInspector = (JavaStringObjectInspector) r.initialize( new ObjectInspector[] { input }); Text forwards = new Text("hello"); when(r.reverse(Mockito.any())).thenReturn("olleh"); Object result = r.evaluate(new GenericUDF.DeferredObject[] { new GenericUDF.DeferredJavaObject(forwards) }); System.out.println(result); assertEquals("olleh", resultInspector.getPrimitiveJavaObject(result)); } }
报错信息
java.lang.NullPointerException at com.hive.udf.ReverseStringTest.testSimpleString(ReverseStringTest.java:34) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
问题分析与解决方案
空指针原因
你当前使用mock(ReverseString.class)创建的是完全模拟对象,Mockito会给所有方法默认返回null。虽然你Mock了init方法,但initialize方法的真实逻辑完全没执行——其中给this.input赋值的步骤被跳过,导致后续调用evaluate时,input为null,触发空指针异常。
正确Mock方式
改用Spy对象(基于真实对象的部分模拟),保留initialize等核心方法的真实逻辑,仅Mock反射相关的init和reverse方法,避免真实调用外部类DummyUdf:
修改后的测试代码:
@RunWith(MockitoJUnitRunner.class) public class ReverseStringTest { @Test public void testSimpleString() throws HiveException { // 创建Spy对象,保留真实对象的核心逻辑 ReverseString r = Mockito.spy(new ReverseString()); ObjectInspector inputInspector = PrimitiveObjectInspectorFactory.javaStringObjectInspector; // 使用doReturn跳过真实init方法的反射逻辑,直接返回指定结果 doReturn(inputInspector).when(r).init(Mockito.any()); // 执行真实的initialize方法,确保input成员变量被正确赋值 JavaStringObjectInspector resultInspector = (JavaStringObjectInspector) r.initialize( new ObjectInspector[] { inputInspector }); Text forwards = new Text("hello"); // Mock reverse方法,直接返回预期的反转结果 doReturn("olleh").when(r).reverse(Mockito.anyString()); // 执行evaluate方法 Object result = r.evaluate(new GenericUDF.DeferredObject[] { new GenericUDF.DeferredJavaObject(forwards) }); // 断言验证结果 assertEquals("olleh", resultInspector.getPrimitiveJavaObject(result)); } }
关键说明
- Spy vs Mock:Spy会执行真实对象的方法逻辑,只有被Mock的方法才会替换行为;而Mock是完全模拟,所有方法默认返回null,这是解决空指针的核心。
- doReturn的使用:用
doReturn(...).when(r).init(...)而不是when(r.init(...)).thenReturn(...),因为后者会先执行一次真实的init方法,可能触发不必要的反射调用,前者直接指定返回值,跳过真实方法执行。 - 代码优化建议:如果后续需要更灵活的测试,可以把反射获取
DummyUdf的逻辑抽成独立方法(比如private DummyUdf getDummyUdfInstance()),这样只需Mock这个方法返回一个Mock的DummyUdf对象,测试会更简洁,也符合单一职责原则。
内容的提问来源于stack exchange,提问作者nambastha
相关产品推荐
相关产品推荐

