Cucumber监听器在Spring Boot上下文初始化前触发致空指针异常
核心问题在于Cucumber的插件(ConcurrentEventListener)初始化和事件触发逻辑早于Spring Boot上下文加载流程。当TestRunStarted事件触发时,@SpringBootTest尚未完成上下文初始化,导致SpringContextHolder.getContext()返回null,抛出NullPointerException。以下是几种可行的解决方案:
方案1:改用Spring+TestNG生命周期钩子替代Cucumber监听器
直接在测试基类中利用TestNG的生命周期注解,确保Spring上下文初始化完成后再启动/停止Appium服务器,无需依赖Cucumber监听器:
修改BaseRunnerTest:
package org.example.core; import io.cucumber.testng.AbstractTestNGCucumberTests; import io.cucumber.testng.CucumberOptions; import org.example.core.services.AppiumServerServices; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @SpringBootTest @CucumberOptions( plugin = {"pretty"}, // 移除自定义监听器 glue = "org.example.stepdefinitions" ) public abstract class BaseRunnerTest extends AbstractTestNGCucumberTests { @Autowired private AppiumServerServices appiumServerServices; @BeforeClass(alwaysRun = true) public void setupAppiumServer() { appiumServerServices.initAppiumServer(); } @AfterClass(alwaysRun = true) public void teardownAppiumServer() { appiumServerServices.tearAppiumServer(); } }
TestNG的@BeforeClass会在所有测试用例执行前触发,且此时Spring上下文已完全初始化,@Autowired可正常注入Bean。此方案最简洁,无生命周期冲突问题,优先推荐。
方案2:改进SpringContextHolder,等待上下文初始化完成
若必须保留Cucumber监听器,可修改SpringContextHolder,监听Spring上下文刷新事件,确保获取上下文时已完成初始化:
package org.example.core.util; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import java.util.concurrent.CountDownLatch; @Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext context; private static final CountDownLatch contextLatch = new CountDownLatch(1); @Override public void setApplicationContext(ApplicationContext applicationContext) { context = applicationContext; } @EventListener(ContextRefreshedEvent.class) public void onContextRefreshed() { contextLatch.countDown(); } public static ApplicationContext getContext() { try { contextLatch.await(); // 阻塞直到上下文初始化完成 } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("等待Spring上下文初始化失败", e); } return context; } }
修改后,AppiumServerListener中的SpringContextHolder.getContext()会等待上下文就绪后再返回,避免NPE。
方案3:使用Cucumber官方Spring集成规范
通过@CucumberContextConfiguration配置Cucumber的Spring上下文,确保Cucumber事件在Spring上下文初始化后触发:
- 创建Cucumber Spring配置类:
package org.example.core.config; import org.springframework.boot.test.context.SpringBootTest; import io.cucumber.spring.CucumberContextConfiguration; @CucumberContextConfiguration @SpringBootTest public class CucumberSpringConfig { }
- 修改
BaseRunnerTest的glue路径,包含配置类所在包:
@CucumberOptions( plugin = {"pretty", "org.example.core.listeners.AppiumServerListener"}, glue = {"org.example.stepdefinitions", "org.example.core.config"} )
此方式符合Cucumber官方Spring集成逻辑,会优先初始化Spring上下文再触发Cucumber事件。
内容的提问来源于stack exchange,提问作者DeepakVerma

