SpringBoot+JUnit5单元测试CI环境加载ApplicationContext失败,本地正常
我编写了一个用于检查SpringBoot应用上下文的简单单元测试,在Windows系统的Android Studio本地运行时完全正常,测试通过,断点调试也能看到数据写入数据库。但在GitHub Actions的CI runner中执行PR验证测试时,却无法加载ApplicationContext,抛出如下异常:
java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context for [WebMergedContextConfiguration@7367f3ce testClass = proj.rpc.database.entities.InventoryEntityUnitTest, locations = [], classes = [proj.App], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@41bcbe97, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@bd9c61a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@23142e20, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@cdd28d1, org.springframework.boot.test.context.SpringBootTestAnnotation@8daa8220], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:145)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260)
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:163)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310)
at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
相关代码
测试用application.yml
spring: datasource: url: jdbc:postgresql://localhost:5432/testcopilot username: mak password: ${POSTGRES_USER_PASSWORD} jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect
App.kt
package proj import org.springframework.boot.CommandLineRunner import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Bean @SpringBootApplication class App { @Bean fun commandLineRunner(ctx: ApplicationContext): CommandLineRunner? { return CommandLineRunner { // do nothing } } } fun main(args: Array<String>) { runApplication<App>(*args) }
UserEntity.kt
package proj.rpc.database.entities import com.fasterxml.jackson.annotation.JsonIgnore import jakarta.persistence.CascadeType import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.OneToMany import jakarta.persistence.Table import java.time.LocalDate @Entity @Table(name = "users") open class UserEntity : Persistable<Long> { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") internal var id: Long? = null @Column(unique = true) private var email: String? = null internal constructor() constructor( email: String ) { this.email = email } override fun getId(): Long? { return id } override fun isNew(): Boolean { return id == null } open fun setEmail(email: String) { this.email = email } open fun getEmail(): String? { return email } }
UserRepository.kt
package proj.rpc.database.repositories import proj.rpc.database.entities.UserEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository interface UserRepository : JpaRepository<UserEntity, Int>
UserRepoUnitTest.kt
package proj.rpc.database.entities import proj.rpc.database.repositories.UserRepository import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.ApplicationContext import org.springframework.transaction.annotation.Transactional import java.time.LocalDate @SpringBootTest @Transactional class UserRepoUnitTest { @Autowired private lateinit var userRepo: UserRepository @Test fun contextLoads(context: ApplicationContext?) { assertNotNull(context) } }
本地环境
使用Android Studio开发,Gradle项目,系统为Windows10,Java版本为17.0.9(通过javac --version查看)。用nektos/act在本地模拟CI环境时,同样会复现该问题,推测是Android Studio与CI runner的环境/配置差异导致。
排查与解决方案
1. 启动CI环境中的PostgreSQL服务
本地环境运行了PostgreSQL,但CI runner默认没有启动该服务,导致应用无法连接数据库,上下文加载失败。
- 解决:在GitHub Actions的Workflow配置中添加PostgreSQL服务:
jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_USER: mak POSTGRES_PASSWORD: test_pass_123 POSTGRES_DB: testcopilot ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
同时确保POSTGRES_USER_PASSWORD环境变量在CI中正确设置,可通过GitHub Secrets或Workflow直接指定。
2. 修正JPA仓库泛型类型不匹配问题
UserRepository定义为JpaRepository<UserEntity, Int>,但UserEntity的ID类型是Long,泛型参数不匹配可能导致上下文加载失败。本地环境可能因缓存未触发问题,但CI环境会严格校验。
- 解决:将UserRepository修正为:
@Repository interface UserRepository : JpaRepository<UserEntity, Long>
3. 检查并统一Java版本
本地使用Java 17.0.9,需确认CI runner的Java版本是否一致,版本差异可能引发兼容性问题。
- 解决:在Workflow中指定Java 17版本:
- name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin'
4. 获取详细错误日志
异常仅提示上下文加载阈值超限,需查看首次加载失败的具体原因。
- 解决:在CI的Gradle测试任务中添加
--info参数,输出详细日志:
- name: Run tests run: ./gradlew test --info
内容的提问来源于stack exchange,提问作者Moazzam Abdullah Khan

