如何处理Spring测试中抛出的UsernameNotFoundException
核心问题定位
你的测试跑不通完全是对象错配+测试模式混用导致的,具体问题如下:
- Mock对象和业务代码的实际依赖完全不匹配
你在测试类中声明的是@Mock UserRepository dataRepository,并对这个对象的findByUsername方法打桩返回测试用户u。但你的UserService在执行loadUserByUsername时,调用的是自身注入的userRepository字段,和你mock的dataRepository没有任何关联。这个真实依赖的Repository没有被mock,默认调用会返回null,直接触发了User can't be found的异常。
这里的变量名误导性极强:你把mock对象命名为dataRepository,但类型是UserRepository,后续写打桩逻辑、校验逻辑时顺理成章都用了这个mock对象,完全没意识到业务代码用的是另一个同类型的依赖。这也解释了为什么你的第二个测试方法没抛异常——findAllUsers方法拿到空结果后直接返回,没有做非空判断抛出异常,但只要Repository返回的是null,就会触发空指针,只是现在刚好返回了空集合没暴露问题。 - 测试模式混用,注解完全没生效
你一边用了Mockito的@Mock注解,一边加了@Autowired想要从Spring容器拿UserService对象:
如果是纯Mockito单元测试,压根不会启动Spring容器,@Autowired的对象会直接是null;
如果是Spring集成测试,@Mock声明的对象不会自动替换容器里的真实Repository Bean,你的mock逻辑根本注入不进业务对象里。 - setUp中的save操作没有任何实际作用
你在setUp方法里写的this.dataRepository.save(u)是调用在mock对象上的,mock对象的方法默认都是无返回、无副作用的空实现,这行代码根本不会把测试用户存到任何地方,属于完全无效的代码。
可直接落地的修正方案
方案1:使用纯Mockito快速单元测试(推荐,无容器启动开销)
- 移除测试类中所有和Spring相关的注解(比如
@Autowired) - 修正mock对象的声明,和业务类依赖保持严格一致:
// 去掉错误的@Mock UserRepository dataRepository,换成和UserService中同类型同语义的mock @Mock UserRepository userRepository; // 如果业务类依赖了DataRepository,再单独加对应mock声明 @Mock DataRepository dataRepository; // 用@InjectMocks自动把上面声明的mock对象注入到UserService实例中 @InjectMocks UserService userService; - 调整打桩逻辑,对准真实依赖的对象:
// 之前是when(dataRepository.findByUsername...),现在改成用正确的userRepository打桩 when(userRepository.findByUsername(anyString())).thenReturn(u); - 删掉setUp中对mock对象调用save的无效代码
- 类上加上Mockito的运行器注解:
@ExtendWith(MockitoExtension.class)
方案2:使用Spring集成测试模式
- 把原来的
@Mock替换为@MockBean,只有这个注解才能把mock对象替换进Spring容器的Bean中 - 同样保持mock的类型和业务类依赖一致:声明
@MockBean UserRepository userRepository,不要混用变量名 - 类上加上对应的Spring测试注解,比如
@SpringBootTest,保证容器正常启动
额外提醒:后续写测试的时候,变量名一定要和被测类的字段名、类型严格对应,否则很容易出现mock对象打了桩但业务代码完全没用到的低级问题,排查起来非常费时。
内容的提问来源于stack exchange,提问作者amelie
相关产品推荐
相关产品推荐

