Spring Boot测试时Predicate中@Autowired注入的repository为null如何实现构造注入
问题根因
你当前使用的是字段注入(@Autowired直接打在成员变量上),只有当NameChangePredicate的实例是由Spring容器管理创建的时候,Spring才会自动注入依赖的NameChangeRepository。但你在测试代码里是直接通过new NameChangePredicate("", zone)手动创建的实例,这个实例不受Spring管控,所以被@Autowired标注的repository自然不会被赋值,始终为null。
生产环境能正常运行,大概率是生产环境里的NameChangePredicate实例是Spring自动创建注入的,没有手动new的操作。
改造方案:构造器注入适配自定义参数
完全可以通过构造器注入同时满足Spring依赖注入和自定义参数传递的需求,改造步骤如下:
1. 改造Predicate类
将需要Spring注入的Repository和自定义参数都放到构造器参数里,Spring 4.3+之后,构造器参数里的Bean会自动注入,不需要额外加@Autowired:
@Slf4j public class NameChangePredicate implements ValidationPredicate { private final NameChangeRepository nameChangeRepository; private final String errorCode; private final GeographicZone zone; // 构造器同时接收Spring托管的Repository和自定义参数 public NameChangePredicate(NameChangeRepository nameChangeRepository, String errorCode, GeographicZone zone) { this.nameChangeRepository = nameChangeRepository; this.errorCode = errorCode; this.zone = zone; } /** * Test the condition * * @param response the {@link ValidationServiceResponse} * @return true if valid */ @Override public boolean test(ValidationServiceResponse response) { if (!StringUtils.isBlank(response.getBookingClass()) && zone != null) { List<NameChange> nameChanges = nameChangeRepository.findAllByGeographicZoneId(zone.getId()); if (nameChanges.stream().anyMatch(n -> n.getClasses().stream().anyMatch(response.getBookingClass()::equalsIgnoreCase))) { return true; } } throw new ValidationException(this.errorCode); } }
注意:你原来的代码里调用了
response.getClass(),这是Object自带的获取类对象的方法,和你实际要取的预订舱位属性冲突,这里已经修正为getBookingClass()和你测试代码里的set方法对应,避免低级错误。
2. 改造测试代码
创建实例的时候把测试类里已经注入成功的repository手动传入即可:
@ActiveProfiles("test") @SpringBootTest public class NameChangeTest { @Autowired NameChangeRepository nameChangeRepository; @Autowired GeographicZoneRepository geographicZoneRepository; private GeographicZone zone; @BeforeEach public void init() { // 你的初始化逻辑 } @Test public void testNameChangeParameter() { // 手动传入已经注入成功的repository final NameChangePredicate predicate = new NameChangePredicate(nameChangeRepository, "", zone); List<NameChange> list = nameChangeRepository.findAll(); Assert.assertEquals("There should be 4 NameChange in db", 4, list.size()); ValidationServiceResponse response = new ValidationServiceResponse(); response.setBookingClass("C"); Assert.assertTrue("Name change should be eligible", predicate.test(response)); } }
3. 生产环境适配
如果生产环境你需要动态创建不同参数的NameChangePredicate,可以封装一个工厂类来托管Repository,简化创建流程:
@Component public class NameChangePredicateFactory { // 工厂类由Spring管理,自动注入Repository private final NameChangeRepository nameChangeRepository; public NameChangePredicateFactory(NameChangeRepository nameChangeRepository) { this.nameChangeRepository = nameChangeRepository; } // 对外提供创建实例的方法,只需要传自定义参数 public NameChangePredicate newInstance(String errorCode, GeographicZone zone) { return new NameChangePredicate(nameChangeRepository, errorCode, zone); } }
生产环境使用的时候直接注入工厂类,调用newInstance方法即可,不需要再手动传Repository:
@Autowired private NameChangePredicateFactory predicateFactory; // 业务代码里创建实例 NameChangePredicate predicate = predicateFactory.newInstance("ERROR_CODE_001", currentZone);
内容的提问来源于stack exchange,提问作者Spialdor

