Search code examples
javaspringspring-bootcucumber-java

How to load Spring Boot Application Context while using Cucumber at runtime, not via tests?


I am working on a Spring Boot application using 1.5.8.RELEASE, which is configured and run via CLI. The parameters are used to configure the application to run in one or more different ways. One of the commands is to run all cucumber features and is called by using:

cucumber.api.cli.Main.main(cucumberArguments);

The current arguments passed include: featureFolder, -g, stepsPackage, and a couple of cucumber-report configuration parameters.

I would like to be able to configure a few properties in my CucumberSteps*.java files via my application.properties file, or via profile-specific application.properties files, but the way I am running the features at the moment means that the Spring Boot context is not loaded.


My first attempt was:

@ConfigurationProperties
public class CucumberStepsFeature1 {

    @Value("${my.property}")
    private String myProperty;

    @Given("...")
    public void given() {
        // fails below
        assertNotNull(this.myProperty);
    }
}

My second attempt at working around this issue was:

@ContextConfiguration(classes = MyMainApp.class)
@ConfigurationProperties
public class CucumberStepsFeature1 {

    @Value("${my.property}")
    private String myProperty;

    @Given("...")
    public void given() {
        // fails below
        assertNotNull(this.myProperty);
    }
}

but I get an error message of

Caused by: javax.management.InstanceAlreadyExistsException: org.springframework.boot:type=Admin,name=SpringApplication

I tried following the steps listed here, but to no avail.


I'd appreciate any attempts to help, but I'll note now that due to company policy, what I share here will be very limited. I won't be able to copy or paste any real code snippets or logs.


Solution

  • Below is the cleanest way I could find to accomplish what I wanted:

    Create a Config class to load in the Spring application properties that you need (application.properties and/or application-${PROFILE}.properties), such as below:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ConfigurationProperties
    public class PropsConfig {
    
        @Value("${my.property}")
        private String myProperty;
    
        //any other properties...
    
        public String getMyProperty() { 
            return this.myProperty; 
        }
    

    Create another class to act as a container for the Spring ApplicationContext. Make sure that this class is scanned by Spring by putting it in a subpackage of your main application class. See code below:

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class SpringApplicationContextContainer implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        @Autowired
        public SpringApplicationContextContainer(ApplicationContext applicationContext) {
            SpringApplicationContextContainer.applicationContext = applicationContext;
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            SpringApplicationContextContainer.applicationContext = applicationContext;
        }
    
        public static ApplicationContext getApplicationContext() {
            return SpringApplicationContextContainer.applicationContext;
        }
    }
    

    Finally, in your CucumberSteps class, or in any other non-Spring class, you can simply make a call to SpringApplicationContextContainer.getApplicationContext(); as below:

    public class CucumberStepsFeature1 {
    
        private final PropsConfig propsConfig;
    
        public CucumberStepsFeature1() {
            this.propsConfig = SpringApplicationContextContainer.getApplicationContext().getBean(PropsConfig.class);
        }
    
        @Given("...")
        public void given() {
            // passes below
            assertNotNull(this.propsConfig);
        }