Mockito中@InjectMocks注入已new初始化类变量的原理疑问
关于Mockito @InjectMocks注入非Spring Bean/硬编码依赖的疑问解答
嘿,我完全懂你的困惑——毕竟按照依赖注入(DI)的最佳实践,我们都知道要避免直接用new硬编码依赖,而是通过构造注入、Setter注入或者Spring容器来解耦。那Mockito的@InjectMocks到底是怎么把你手动new出来的LDAPGroupAccessor给替换掉的?这看起来确实有点反直觉对吧?
一、@InjectMocks的底层实现逻辑
Mockito的@InjectMocks根本不依赖Spring的DI容器,它是靠反射机制完成注入的,具体流程大概是这样:
- 首先,Mockito会创建被测试类(也就是你的
AuthorizationHandler)的实例——如果没指定构造方式,就用无参构造器生成。 - 接着,它会扫描被测试类里的所有字段,不管这个字段是怎么初始化的(哪怕是你用
new硬编码出来的)。 - 最后,它会在测试类里寻找类型匹配的
@Mock(或@Spy)实例,通过反射把这个mock对象赋值给被测试类的对应字段,直接覆盖掉原来的实例。
放到你的代码里说:AuthorizationHandler里的groupAccessor = new LDAPGroupAccessor(),在@InjectMocks生效后,这个字段会被反射替换成你用@Mock注解创建的那个mock实例,所以你在setup()里对mock的配置,才能在测试中被authorizationHandler调用到。
二、这算不算违背依赖注入的初衷?
这里得区分生产代码和测试代码来看:
- 在生产代码里,直接用
new实例化依赖确实违背了DI的初衷——它让类之间耦合度极高,既难以替换依赖、扩展功能,也不符合控制反转(IoC)的思想。所以在实际项目中,更合理的写法应该是把LDAPGroupAccessor做成Spring Bean,然后通过构造注入的方式传入:@Component public class AuthorizationHandler { private final LDAPGroupAccessor groupAccessor; // 构造注入,完全符合DI规范 public AuthorizationHandler(LDAPGroupAccessor groupAccessor) { this.groupAccessor = groupAccessor; } public boolean isUserAuthorized(String userId, String groupId){ return groupAccessor.isUserInGroup(userId, groupId); } } - 但在测试代码里,
@InjectMocks是Mockito提供的一种妥协方案——它允许我们在不修改生产代码(或者暂时没来得及优化的情况下),依然能对耦合度高的类进行单元测试。它的存在是为了降低测试门槛,而不是鼓励我们在生产代码里放弃DI。
三、你的测试代码为什么能生效?
结合你的代码具体来看:
- 你用
@Mock创建了一个LDAPGroupAccessor的mock实例; @InjectMocks会自动创建AuthorizationHandler的实例,然后通过反射把mock的groupAccessor替换掉原来new出来的那个;- 所以你在
setup()里对mock的配置,会在test1()中被authorizationHandler调用,最终测试通过。
不过还是要提一句:虽然@InjectMocks能解决燃眉之急,但长期来看,优化生产代码的依赖注入方式才是更符合软件工程规范的做法——这样不仅测试逻辑更清晰,生产代码的可维护性和扩展性也会大大提升。
内容的提问来源于stack exchange,提问作者G8XSU
相关产品推荐
相关产品推荐

