如何模拟带有@PostConstruct方法的Bean?解决初始化阶段空指针异常问题
你的问题根源在于Mockito直接mockMyClass时,不会执行原类的构造器逻辑,导致mapper字段未被赋值,而Spring会在Bean创建后调用@PostConstruct方法,这时自然触发空指针异常。
下面提供两种可行的解决方案,推荐第一种更简洁的方式:
方案一:用Mockito.spy包装真实的MyClass实例
既然你需要MyClass的mock对象,但又要保留它的构造器注入和@PostConstruct初始化逻辑,可以创建真实的MyClass实例(传入ObjectMapper),再用spy包装它——这样既保留了正常的初始化流程,又能mock你需要的方法。
修改你的Config类中的myClass()方法:
@Bean MyClass myClass(ObjectMapper mapper) { // 创建真实实例并spy,构造器会被调用,mapper被注入 return Mockito.spy(new MyClass(mapper)); }
这样处理后,MyClass的@PostConstruct方法执行时,mapper已经被正确赋值,不会再出现空指针;同时这个spy对象可以正常注入到MyService中,你也可以用Mockito的API来mock它的其他方法行为。
方案二:手动为mock对象注入依赖并触发初始化
如果你必须使用纯mock(不创建真实实例),可以通过反射为mock对象设置mapper字段,再手动调用@PostConstruct方法:
import org.springframework.test.util.ReflectionTestUtils; import java.lang.reflect.Method; // ... @Bean MyClass myClass(ObjectMapper mapper) { MyClass myClassMock = Mockito.mock(MyClass.class); // 用反射设置mapper字段 ReflectionTestUtils.setField(myClassMock, "mapper", mapper); // 手动调用@PostConstruct的init方法 try { Method initMethod = MyClass.class.getDeclaredMethod("init"); initMethod.setAccessible(true); initMethod.invoke(myClassMock); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Failed to execute @PostConstruct method", e); } return myClassMock; }
这种方式需要处理反射相关的异常,而且如果@PostConstruct方法的签名或访问权限变化,代码需要同步调整,所以仅在特殊场景下使用。
为什么原代码会出错?
当你调用Mockito.mock(MyClass.class)时,Mockito会生成一个动态代理对象,这个对象不会调用原类的构造器,因此MyClass中的mapper字段始终为null。而Spring会在Bean注册完成后自动调用@PostConstruct方法,这时访问mapper就会触发NullPointerException。
内容的提问来源于stack exchange,提问作者John

