You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:24:57