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

SpringBoot中ApplicationReadyEvent的E2E测试环境准备问题

问题描述

我正尝试为监听ApplicationReadyEvent的组件编写E2E测试,需要在事件触发前用LocalStack搭建基础设施。但测试运行时,ApplicationReadyEvent会在beforeEach及测试方法执行前就触发,导致我无法完成环境准备或mock操作,求解决方案。

业务类代码

@Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
    private final MyService myService;

    public ApplicationStartup(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void onApplicationEvent(final ApplicationReadyEvent event) {
        myService.myMethod();
    }
}

测试类代码

@SpringBootTest
@Testcontainers
class ApplicationStartupE2E {

    @Container
    static LocalStackContainer localStack = LocalStackConstants.createLocalStackContainer();

    @SpyBean
    private MyService myService;


    @DynamicPropertySource
    static void overrideProperties(DynamicPropertyRegistry registry) {
        registry.add("amazon.aws.region", () -> localStack.getRegion());
        registry.add("amazon.aws.access-key", () -> localStack.getAccessKey());
        registry.add("amazon.aws.secret-key", () -> localStack.getSecretKey());

        registry.add("amazon.s3.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.S3).toString());
        registry.add("amazon.dynamodb.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());

    }

    @BeforeEach
    void setUp() {
        LocalStackFunctions.doStuff(localStack);
    }

    @AfterEach
    void tearDown() {
        LocalStackFunctions.doStuff(localStack);
    }

    @Test
    void shouldTriggerProcess() {
       // Additional mocks if needed
       // Trigger event
       // Assert
    }
}
解决思路

思路1:加开关控制,手动触发启动逻辑

给业务类加个配置开关,默认开启自动执行,测试时关闭,等环境和mock都准备好后再手动触发启动逻辑。

修改业务类:

@Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
    private final MyService myService;
    @Value("${application.startup.enabled:true}")
    private boolean enabled;

    public ApplicationStartup(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void onApplicationEvent(final ApplicationReadyEvent event) {
        if (enabled) {
            myService.myMethod();
        }
    }

    // 暴露手动触发的方法
    public void triggerStartup() {
        myService.myMethod();
    }
}

修改测试类:

@SpringBootTest(properties = "application.startup.enabled=false")
@Testcontainers
class ApplicationStartupE2E {

    @Container
    static LocalStackContainer localStack = LocalStackConstants.createLocalStackContainer();

    @SpyBean
    private MyService myService;
    @Autowired
    private ApplicationStartup applicationStartup;

    @DynamicPropertySource
    static void overrideProperties(DynamicPropertyRegistry registry) {
        registry.add("amazon.aws.region", () -> localStack.getRegion());
        registry.add("amazon.aws.access-key", () -> localStack.getAccessKey());
        registry.add("amazon.aws.secret-key", () -> localStack.getSecretKey());

        registry.add("amazon.s3.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.S3).toString());
        registry.add("amazon.dynamodb.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());

    }

    @BeforeEach
    void setUp() {
        LocalStackFunctions.doStuff(localStack);
        // 这里可以放心做mock操作
        doNothing().when(myService).someDependencyMethod();
    }

    @AfterEach
    void tearDown() {
        LocalStackFunctions.doStuff(localStack);
    }

    @Test
    void shouldTriggerProcess() {
       // 环境和mock都准备好后,手动触发启动逻辑
       applicationStartup.triggerStartup();
       // 验证方法被调用
       verify(myService, times(1)).myMethod();
    }
}

思路2:测试时替换监听器,延迟执行

在测试里用@TestConfiguration替换原有的ApplicationStartup组件,让它不自动执行逻辑,等测试准备就绪后再手动触发真实的启动逻辑。

修改测试类:

@SpringBootTest
@Testcontainers
class ApplicationStartupE2E {

    @Container
    static LocalStackContainer localStack = LocalStackConstants.createLocalStackContainer();

    @SpyBean
    private MyService myService;
    @Autowired
    private ApplicationContext context;

    @DynamicPropertySource
    static void overrideProperties(DynamicPropertyRegistry registry) {
        registry.add("amazon.aws.region", () -> localStack.getRegion());
        registry.add("amazon.aws.access-key", () -> localStack.getAccessKey());
        registry.add("amazon.aws.secret-key", () -> localStack.getSecretKey());

        registry.add("amazon.s3.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.S3).toString());
        registry.add("amazon.dynamodb.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());

    }

    // 自定义测试配置,替换原组件
    @TestConfiguration
    static class TestConfig {
        @Bean
        @Primary
        public ApplicationStartup applicationStartup(MyService myService) {
            // 返回一个空实现的监听器,不自动执行逻辑
            return new ApplicationStartup(myService) {
                @Override
                public void onApplicationEvent(ApplicationReadyEvent event) {
                    // 空实现
                }
            };
        }
    }

    @BeforeEach
    void setUp() {
        LocalStackFunctions.doStuff(localStack);
        // 这里做mock
    }

    @Test
    void shouldTriggerProcess() {
       // 创建真实的监听器实例
       ApplicationStartup realStartup = new ApplicationStartup(myService);
       // 手动触发事件
       realStartup.onApplicationEvent(new ApplicationReadyEvent(context));
       // 断言方法被调用
       verify(myService, times(1)).myMethod();
    }
}

思路3:提前完成LocalStack准备

如果不想修改业务代码,就把LocalStack的准备工作提前到Spring上下文启动前,用@BeforeAll代替@BeforeEach,确保环境在ApplicationReadyEvent触发前就配置完成。

修改测试类:

@SpringBootTest
@Testcontainers
class ApplicationStartupE2E {

    @Container
    static LocalStackContainer localStack = LocalStackConstants.createLocalStackContainer();

    @SpyBean
    private MyService myService;

    // 提前在Spring上下文启动前完成LocalStack配置
    @BeforeAll
    static void prepareLocalStack() {
        LocalStackFunctions.doStuff(localStack);
    }

    @DynamicPropertySource
    static void overrideProperties(DynamicPropertyRegistry registry) {
        registry.add("amazon.aws.region", () -> localStack.getRegion());
        registry.add("amazon.aws.access-key", () -> localStack.getAccessKey());
        registry.add("amazon.aws.secret-key", () -> localStack.getSecretKey());

        registry.add("amazon.s3.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.S3).toString());
        registry.add("amazon.dynamodb.endpoint", () -> localStack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());

    }

    @AfterAll
    static void cleanUpLocalStack() {
        LocalStackFunctions.doStuff(localStack);
    }

    @Test
    void shouldTriggerProcess() {
       // 此时ApplicationReadyEvent已经触发,直接验证
       verify(myService, times(1)).myMethod();
    }
}

内容的提问来源于stack exchange,提问作者magalenyo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:25:19