Search code examples
springspring-bootconfigurationspring-testspring-profiles

Spring Boot property resolution fails tests at build time when adding a profile


I have a Spring Boot 2.7.14 application packaged as follows:

com.abc.globalpayments.feeds.downstream.dailycashreport com.abc.globalpayments.feeds.downstream.dailycashreport.acquire com.abc.globalpayments.feeds.downstream.dailycashreport.acquire.random com.abc.globalpayments.feeds.downstream.dailycashreport.distribute com.abc.globalpayments.feeds.downstream.dailycashreport.domain com.abc.globalpayments.feeds.downstream.dailycashreport.generate com.abc.globalpayments.feeds.downstream.dailycashreport.process

The root *.dailycashreport package contains the entry point:

@Slf4j
@SpringBootApplication
@PropertySource(value = "classpath:feeds-config.yml", factory = com.abc.globalpayments.feeds.downstream.YamlPropertySourceFactory.class)
public class DcrDataFactoryApplication implements CommandLineRunner {
    

    
    @Autowired
    private SyntheticRunner runner; data sources

    public static void main(String[] args) {//}

YamlPropertySourceFactory seen above is as follows:

public class YamlPropertySourceFactory implements PropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
        factory.setResources(encodedResource.getResource());

        Properties properties = factory.getObject();

        return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
    }
}

There are the following 3 resources under src/main/resources:

application.yml:

spring:
  application:
    name: daily-cash-report-application

sftp:
  host: dev
  port: 22
  user: dev
  stagingDir: /opt/ccc/Data/Stage
  tmpDir: /opt/ccc/abcd/tmp
  #privateKey: 
  privateKey: file:///C:/Users/12345/AppData/Roaming/SSH/UserKeys/distributessh
  chmod: 664 
  
file:    
  stagingDir: /opt/ccc/Data/Stage
  tmpDir: C:\tmp 

feeds-config.yml

feeds:
  downstream:
    another-system:
      output-file-name: other.txt
    daily-cash-report:
      output-file-name: dcr.txt
      test:
        records-to-generate:
          pc: 10
          dp: 5
          z: 1

and logback.xml


In tests, I'm using the following setup:

@SpringBootTest
class DataMarshallerServiceTest {

    @Autowired
    private DataMarshallerService cut;
    
    @Autowired
//

The above setup builds and works fine, however as soon as I'm trying to introduce a dev profile by renaming application.yml into application-dev.yml, I start encountering the following exception as part of the build in tests execution:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'sftp.host' in value "${sftp.host}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180)

Here's the launch profile in my STS IDE:

enter image description here

However, the error manifests during the build time, when executing

mvn clear package -P dcr

which is equivalent to the following STS Maven config also present in STS:

enter image description here

What is it I'm missing so that Spring profiles start functioning properly?

Could it be that since the *App has YamlPropertySourceFactory configured, the rest of the properties need to be specified explicitly?

Thank you in advance.


Solution

  • I've made the following assumption and an attempt to validate it, it seems to work, so I'll leave it here as a potential answer to be, potentially, challenged.

    Apparently tests need to be pointed at the proper profile which is not part of the @SpringBootTest annotation itself.

    Either one of the following 3 configurations seem to make the build work:

    Option 1:

    I've updated all the failing test cases with the following annotation after the @SpringBootTest:

    @ActiveProfiles(profiles = "dev")
    

    while adding the following stanza inside the application.yml:

    spring:
      application:
        name: daily-cash-report-application
      profiles: dev
    

    It also works without the above stanza if I rename application.yml into application-dev.yml.

    Option 2:

    Another, even simpler approach is as follows:

    1. no need to annotate all tests with additional @ActiveProfiles(profiles = "dev") as above while
    2. running the build with the following Maven command instead:
    mvn clean package -P dcr -Dspring.profiles.active=dev
    

    and

    1. consuming the application-dev.yml out of src/main/resources/

    Option 3

    No need to either:

    • adding an application-dev.yml file
    • or specifying an additional @ActiveProfiles(profiles = "dev") annotation
    • or needing to point to a specific profile as part of invocation of the build with -Dspring.profiles.active=dev,
    • or specifying -Dspring.profiles.active=dev as a parameter to Maven

    Simply:

    Drop a copy of application.yml into src/test/resources/ folder so as to be picked up and used for tests execution via mvn clean package -P dcr

    I'll let someone more authoritative to confirm that these are the right approaches and to validate that this test behavior in Spring is by design.

    Thank you for reading.