Spring 5.2.0.RELEASE中JPA Repository单元测试空指针及启动错误排查
Spring 5.2.0.RELEASE下JPA Repository测试问题修复
问题背景
在Spring 5.2.0.RELEASE环境中,配置H2内存数据源与JPA相关Bean后,编写的Repository测试出现两个问题:
- 未添加
@RunWith时,@Autowired注入的AutorisationSortieRepository为空,调用save触发空指针异常 - 添加
@RunWith(SpringJUnit4ClassRunner.class)后,出现TestContextBootstrapper加载失败,根源是找不到org.springframework.util.ConcurrentLruCache类
相关代码
TestConfig配置类
@Configuration @EnableJpaRepositories(basePackages = "es.calero.repositories") public class TestConfig { @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"); dataSource.setUsername("sa"); dataSource.setPassword(""); return dataSource; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("es.calero.model.inscriptions"); JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); return em; } }
AutorisationSortieRepositoryTest测试类
@ContextConfiguration(classes = es.calero.repositories.utils.TestConfig.class) @Transactional public class AutorisationSortieRepositoryTest { @Autowired private AutorisationSortieRepository autorisationSortieRepository; private es.calero.model.autorisation.AutorisationSortie autorisationSortie; @Before public void setUp() { // 插入测试数据到内存库 autorisationSortie = new AutorisationSortie(); autorisationSortie.setEnfantId(1L); autorisationSortie.setDtDebut(new Date()); autorisationSortie.setDtFin(new Date(System.currentTimeMillis() + 86400000L)); // +1天 autorisationSortieRepository.save(autorisationSortie); } @Test public void testFindActiveAutorisationSortieByEnfantId() { // 创建一个在有效期内的参考日期 Date dtRef = new Date(System.currentTimeMillis() + 43200000L); // +12小时 List<AutorisationSortie> result = autorisationSortieRepository.findActiveAutorisationSortieByEnfantId(1L, dtRef); // 断言结果非空且包含测试数据 Assert.assertTrue("结果不应为空", !result.isEmpty()); assertEquals("结果应包含测试的AutorisationSortie", autorisationSortie, result.get(0)); } @Test public void testFindActiveAutorisationSortieByEnfantId_NoResult() { // 创建一个在有效期外的参考日期 Date dtRef = new Date(System.currentTimeMillis() - 86400000L); // -1天 List<AutorisationSortie> result = autorisationSortieRepository.findActiveAutorisationSortieByEnfantId(1L, dtRef); // 断言结果为空 Assert.assertTrue("结果应为空", result.isEmpty()); } }
添加@RunWith后的错误栈
initializationError(es.calero.repositories.autorisation.AutorisationSortieRepositoryTest) Time elapsed: 0.011 sec <<< ERROR! java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]. Specify @BootstrapWith's 'value' attribute or make the default bootstrapper class available. at org.springframework.test.context.BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.java:155) at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:122) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:151) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:142) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104) at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59) at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:33) at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:250) at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141) at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189) at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165) at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85) at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115) at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75) Caused by: java.lang.NoClassDefFoundError: org/springframework/util/ConcurrentLruCache at org.springframework.test.context.TestContextAnnotationUtils.<clinit>(TestContextAnnotationUtils.java:74) at org.springframework.test.context.BootstrapUtils.resolveExplicitTestContextBootstrapper(BootstrapUtils.java:165) at org.springframework.test.context.BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.java:138) ... 25 more Caused by: java.lang.ClassNotFoundException: org.springframework.util.ConcurrentLruCache at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ... 28 more Results : Tests in error: initializationError(es.calero.repositories.autorisation.AutorisationSortieRepositoryTest): Could not load TestContextBootstrapper [null]. Specify @BootstrapWith's 'value' attribute or make the default bootstrapper class available. Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
解决方案
1. 修复依赖版本不匹配问题
ConcurrentLruCache是Spring 5.2+版本spring-core中的类,出现找不到类的问题,是因为spring-test依赖版本和Spring核心版本不一致。检查你的依赖管理(Maven/Gradle),确保spring-test的版本严格等于5.2.0.RELEASE,和Spring核心组件版本保持一致。
比如Maven中添加:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.0.RELEASE</version> <scope>test</scope> </dependency>
2. 优化测试类注解
用@SpringJUnitConfig替代@ContextConfiguration+@RunWith(SpringJUnit4ClassRunner.class),这是Spring 4.3+推荐的简化写法,自动启用Spring测试上下文:
@SpringJUnitConfig(classes = es.calero.repositories.utils.TestConfig.class) @Transactional public class AutorisationSortieRepositoryTest { // ... 原有代码不变 }
3. 补全配置类缺失的Bean
TestConfig中缺少JPA事务管理器,且实体类扫描包错误:
- 添加
PlatformTransactionManagerBean,JPA测试需要事务支持 - 修改
entityManagerFactory的packagesToScan为es.calero.model.autorisation(你的实体类所在包),否则JPA无法识别实体
修改后的TestConfig:
@Configuration @EnableJpaRepositories(basePackages = "es.calero.repositories") public class TestConfig { @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"); dataSource.setUsername("sa"); dataSource.setPassword(""); return dataSource; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); // 修改为实体类所在包 em.setPackagesToScan("es.calero.model.autorisation"); JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); // 可选:添加JPA属性配置,比如显示SQL Properties props = new Properties(); props.put("hibernate.show_sql", "true"); em.setJpaProperties(props); return em; } // 添加事务管理器Bean @Bean public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(emf); return transactionManager; } }
4. 验证Repository扫描范围
确保@EnableJpaRepositories的basePackages包含AutorisationSortieRepository所在的包,否则Spring无法创建Repository代理类。
内容的提问来源于stack exchange,提问作者Nunyet Calçada
相关产品推荐
相关产品推荐

