115

I'm using a Spring Boot app which runs my src/main/resources/config/application.yml file.

When I run my test case by :

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
public class MyIntTest{
}

The test codes still run my application.yml file to load properties. I wonder if it is possible to run another *.yml file when running the test case.

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
Exia
  • 2,381
  • 4
  • 17
  • 24

13 Answers13

134

One option is to work with profiles. Create a file called application-test.yml, move all properties you need for those tests to that file and then add the @ActiveProfiles annotation to your test class:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
@ActiveProfiles("test") // Like this
public class MyIntTest{
}

Be aware, it will additionally load the application-test.yml, so all properties that are in application.yml are still going to be applied as well. If you don't want that, either use a profile for those as well, or override them in your application-test.yml.

g00glen00b
  • 41,995
  • 13
  • 95
  • 133
  • 13
    This is the correct answer. The annotation TestPropertySource only works for .properties or .xml file. See "Supported File formats sections" in https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/TestPropertySource.html – Sym-Sym Dec 11 '17 at 20:35
  • 3
    This works nicely but if you do this I would advise that you also have some tests that **don't** use the "test" profile. The fact that the test application yaml is merged with the actual application yml can cause the application to run fine in unit test but then have issues when run standalone. – Joman68 Mar 05 '19 at 20:31
  • I guess that's the best way to test if the configuration file is formatted correctly for a profile. This means that we would need N tests empty tests just loading the Spring context for each of the N profiles to test that, any other ideas? – Dio Oct 10 '19 at 08:01
40

You can set your test properties in src/test/resources/config/application.yml file. Spring Boot test cases will take properties from application.yml file in test directory.

The config folder is predefined in Spring Boot.

As per documentation:

If you do not like application.properties as the configuration file name, you can switch to another file name by specifying a spring.config.name environment property. You can also refer to an explicit location by using the spring.config.location environment property (which is a comma-separated list of directory locations or file paths). The following example shows how to specify a different file name:

java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

The same works for application.yml

Documentation:

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-application-property-files

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-application-property-files

Fenio
  • 3,528
  • 1
  • 13
  • 27
TheKojuEffect
  • 20,103
  • 19
  • 89
  • 125
  • 4
    Actually , at the very first ,the yml file for testing is at that path, i.e."src/test/resources/config/application.yml".But I don't know why it is not loaded when I ran my test case – Exia Aug 03 '16 at 16:57
  • 1
    I was using Intellij IDEA and did not have `**/*.yml` in the `` of my `pom.xml`. As IDEA uses the `pom.xml` configuration as its primary model for compiling your code (even outside of `mvn` runs!), this meant that my `src/test/resources/application.yml` was not being picked up by my test, because it was not present in `target/test-classes`. – Rossiar Dec 14 '16 at 10:45
  • 6
    This should be marked as correct answer, because it is the case for YAML files. I have confirmed with Spring Boot Team that TestPropertySource does not support YAML files.[Spring Boot JIRA](https://jira.spring.io/browse/SPR-16563) – selman Mar 07 '18 at 14:32
  • Does anybody know how to force springboot to replace @placeholder@ in yml while testing? Why they force me to statically input data? – Simon Logic Oct 19 '18 at 15:20
  • 2
    Note that name of the subdirectory (`config`) isn't arbitrary but is predefined. For details see [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-application-property-files) and the source code of the ConfigFileApplicationListener.load() method. – Ilya Serbis Nov 14 '18 at 21:37
  • @SimonLogic Checkout Maven resources plugin – TheKojuEffect Nov 14 '18 at 22:38
  • Hi @TheKojuEffect, are you sure that Spring Boot test cases will take properties from test directory? The [Externalized Configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config) did not say that. – jumping_monkey Mar 03 '20 at 03:11
  • Terrible behavior. Separation of concerns maybe??? configs from the src dir have NOTHING to do with running tests. If anything, a useful message should be logged, stating that no default config could be found in the default location they describe. – html_programmer Jul 27 '22 at 11:48
22

You can use @TestPropertySource to load different properties/yaml file

@TestPropertySource(locations="classpath:test.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class MyIntTest{

}

OR if you want to override only specific properties/yaml you can use

@TestPropertySource(
        properties = {
                "spring.jpa.hibernate.ddl-auto=validate",
                "liquibase.enabled=false"
        }
)
A0__oN
  • 8,740
  • 6
  • 40
  • 61
  • 6
    I tried using this annotation but in the test it still looks for the `application.yml` instead of the one I specify. – ttt Jan 26 '18 at 02:48
  • 6
    Like explained already, this works only for .properties file, as the annotation does not have support for yaml files. This really is not an answer to the question. – eis Sep 15 '21 at 09:40
17

If you need to have production application.yml completely replaced then put its test version to the same path but in test environment (usually it is src/test/resources/)

But if you need to override or add some properties then you have few options.

Option 1: put test application.yml in src/test/resources/config/ directory as @TheKojuEffect suggests in his answer.

Option 2: use profile-specific properties: create say application-test.yml in your src/test/resources/ folder and:

  • add @ActiveProfiles annotation to your test classes:

    @SpringBootTest(classes = Application.class)
    @ActiveProfiles("test")
    public class MyIntTest {
    
  • or alternatively set spring.profiles.active property value in @SpringBootTest annotation:

    @SpringBootTest(
            properties = ["spring.profiles.active=test"],
            classes = Application.class,
    )
    public class MyIntTest {
    

This works not only with @SpringBootTest but with @JsonTest, @JdbcTests, @DataJpaTest and other slice test annotations as well.

And you can set as many profiles as you want (spring.profiles.active=dev,hsqldb) - see farther details in documentation on Profiles.

Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
  • 1
    This is correct. I have tested these scenarios and it worked. First I have created `application.yml` for jUnit purpose and put it under `src/test/resources/` (same path as the main application.yml) and it completely replacing the original yaml file ( not overwrite). Then I created `application-test.yml` instead of `application.yml` under `src/test/resources/` and using `@ActiveProfiles("test")`, I could see jUnit is taking property values from `application-test.yml` (overwriting `application.yml` for common properties) and then taking values from `application.yml` as well. – Surya Aug 31 '22 at 11:04
14

Spring-boot framework allows us to provide YAML files as a replacement for the .properties file and it is convenient.The keys in property files can be provided in YAML format in application.yml file in the resource folder and spring-boot will automatically take it up.Keep in mind that the yaml format has to keep the spaces correct for the value to be read correctly.

You can use the @Value("${property}") to inject the values from the YAML files. Also Spring.active.profiles can be given to differentiate between different YAML for different environments for convenient deployment.

For testing purposes, the test YAML file can be named like application-test.yml and placed in the resource folder of the test directory.

If you are specifying the application-test.yml and provide the spring test profile in the .yml, then you can use the @ActiveProfiles('test') annotation to direct spring to take the configurations from the application-test.yml that you have specified.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationTest.class)
@ActiveProfiles("test")
public class MyTest {
 ...
}

If you are using JUnit 5 then no need for other annotations as @SpringBootTest already include the springrunner annotation. Keeping a separate main ApplicationTest.class enables us to provide separate configuration classes for tests and we can prevent the default configuration beans from loading by excluding them from a component scan in the test main class. You can also provide the profile to be loaded there.

@SpringBootApplication(exclude=SecurityAutoConfiguration.class)
public class ApplicationTest {
 
    public static void main(String[] args) {
        SpringApplication.run(ApplicationTest.class, args);
    }
}

Here is the link for Spring documentation regarding the use of YAML instead of .properties file(s): https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Fenio
  • 3,528
  • 1
  • 13
  • 27
Ananthapadmanabhan
  • 5,706
  • 6
  • 22
  • 39
5

See this: Spring @PropertySource using YAML

I think the 3rd answer has what you're looking for, i.e have a separate POJO to map your yaml values into:

@ConfigurationProperties(path="classpath:/appprops.yml", name="db")
public class DbProperties {
    private String url;
    private String username;
    private String password;
...
}

Then annotate your test class with this:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}
Community
  • 1
  • 1
Pete
  • 1,500
  • 2
  • 16
  • 33
5

A simple working configuration using

@TestPropertySource and properties

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource(properties = {"spring.config.location=classpath:another.yml"})
public class TestClass {


    @Test

    public void someTest() {
    }
}
Dhruv
  • 10,291
  • 18
  • 77
  • 126
3

Starting with Spring 4.1, We can directly set the property in application.yml using the @TestPropertySource annotation.

@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = {"yoursection.yourparameter=your_value"})
public MyIntTest
{
 //your test methods
}

Just convert your yaml parameters into complete property structure. For example: If content of application.yml is like below

yoursection:
  yourparameter:your_value

Then value to go inside the @TestPropertySource will be,

yoursection.yourparameter=your_value
girivasan4
  • 184
  • 2
  • 7
2

This might be considered one of the options. now if you wanted to load a yml file ( which did not get loaded by default on applying the above annotations) the trick is to use

@ContextConfiguration(classes= {...}, initializers={ConfigFileApplicationContextInitializer.class})

Here is a sample code

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@DirtiesContext
@ContextConfiguration(classes= {DataSourceTestConfig.class}, initializers = {ConfigFileApplicationContextInitializer.class})
public class CustomDateDeserializerTest {


    private ObjectMapper objMapper;

    @Before
    public void setUp() {
        objMapper = new ObjectMapper();

    }

    @Test
    public void test_dateDeserialization() {

    }
}

Again make sure that the setup config java file - here DataSourceTestConfig.java contains the following property values.

@Configuration
@ActiveProfiles("test")
@TestPropertySource(properties = { "spring.config.location=classpath:application-test.yml" })
public class DataSourceTestConfig implements EnvironmentAware {

    private Environment env;

    @Bean
    @Profile("test")
    public DataSource testDs() {
       HikariDataSource ds = new HikariDataSource();

        boolean isAutoCommitEnabled = env.getProperty("spring.datasource.hikari.auto-commit") != null ? Boolean.parseBoolean(env.getProperty("spring.datasource.hikari.auto-commit")):false;
        ds.setAutoCommit(isAutoCommitEnabled);
        // Connection test query is for legacy connections
        //ds.setConnectionInitSql(env.getProperty("spring.datasource.hikari.connection-test-query"));
        ds.setPoolName(env.getProperty("spring.datasource.hikari.pool-name"));
        ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        long timeout = env.getProperty("spring.datasource.hikari.idleTimeout") != null ? Long.parseLong(env.getProperty("spring.datasource.hikari.idleTimeout")): 40000;
        ds.setIdleTimeout(timeout);
        long maxLifeTime = env.getProperty("spring.datasource.hikari.maxLifetime") != null ? Long.parseLong(env.getProperty("spring.datasource.hikari.maxLifetime")): 1800000 ;
        ds.setMaxLifetime(maxLifeTime);
        ds.setJdbcUrl(env.getProperty("spring.datasource.url"));
        ds.setPoolName(env.getProperty("spring.datasource.hikari.pool-name"));
        ds.setUsername(env.getProperty("spring.datasource.username"));
        ds.setPassword(env.getProperty("spring.datasource.password"));
        int poolSize = env.getProperty("spring.datasource.hikari.maximum-pool-size") != null ? Integer.parseInt(env.getProperty("spring.datasource.hikari.maximum-pool-size")): 10;
        ds.setMaximumPoolSize(poolSize);

        return ds;
    }

    @Bean
    @Profile("test")
    public JdbcTemplate testJdbctemplate() {
        return new JdbcTemplate(testDs());
    }

    @Bean
    @Profile("test")
    public NamedParameterJdbcTemplate testNamedTemplate() {
        return new NamedParameterJdbcTemplate(testDs());
    }

    @Override
    public void setEnvironment(Environment environment) {
        // TODO Auto-generated method stub
        this.env = environment;
    }
}
vijayakumarpsg587
  • 1,079
  • 3
  • 22
  • 39
2

Lu55 Option 1 how to...

Add test only application.yml inside a seperated resources folder.

├── main
│   ├── java
│   └── resources
│       ├── application.yml
└── test
    ├── java
    └── resources
        └── application.yml

In this project structure the application.yml under main is loaded if the code under main is running, the application.yml under test is used in a test.

To setup this structure add a new Package folder test/recources if not present.

Eclipse right click on your project -> Properties -> Java Build Path -> Source Tab -> (Dialog ont the rigth side) "Add Folder ..."

Inside Source Folder Selection -> mark test -> click on "Create New Folder ..." button -> type "resources" inside the Textfeld -> Click the "Finish" button.

After pushing the "Finisch" button you can see the sourcefolder {projectname}/src/test/recources (new)

Optional: Arrange folder sequence for the Project Explorer view. Klick on Order and Export Tab mark and move {projectname}/src/test/recources to bottom. Apply and Close

!!! Clean up Project !!!
Eclipse -> Project -> Clean ...

Now there is a separated yaml for test and the main application.

Pinocchio
  • 51
  • 3
1

I have tested @IlyaSerbis answer and it is working for me. I am using Spring Boot 2.3.

I have tested these scenarios and it worked.

Scenario1 - First I have created application.yml for jUnit purpose and put it under src/test/resources/config (same path as the main application.yml) and it completely replacing the original yaml file ( not overwrite). That means it will not use any of the properties mentioned in application.yml of src/main/resources/config directory (main directory).

Note: In Scenario1, the path of application.yml in main and test need to be exactly same. If src/main/resources/config/application.yml then src/test/resources/config/application.yml. If src/main/resources/application.yml then src/test/resources/application.yml

Scenario2 - Then I created application-test.yml instead of application.yml under src/test/resources/config and using @ActiveProfiles("test"), I could see jUnit is taking property values from application-test.yml (overwriting application.yml for common properties) and then taking values from application.yml as well which is not mentioned in application-test.yml.

Surya
  • 604
  • 1
  • 6
  • 25
  • using 2.3.12.RELEASE, application.yml under src/test/resources/config seems be merged by overring its values over that of src/main/resources/application.yml – user1767316 Aug 24 '23 at 15:49
0

We can use @SpringBootTest annotation which loads the yml file from src\main\java\com...hence when we execute the unit test, all of the properties are already there in the config properties class.

@RunWith(SpringRunner.class)
@SpringBootTest
public class AddressFieldsTest {

    @InjectMocks
    AddressFieldsValidator addressFieldsValidator;

    @Autowired
    AddressFieldsConfig addressFieldsConfig;
    ...........

    @Before
    public void setUp() throws Exception{
        MockitoAnnotations.initMocks(this);
        ReflectionTestUtils.setField(addressFieldsValidator,"addressFieldsConfig", addressFieldsConfig);
    }

}

We can use @Value annotation if you have few configs or other wise we can use a config properties class. For e.g

@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "address.fields.regex")
public class AddressFieldsConfig {

    private int firstName;
    private int lastName;
    .........
denzal
  • 1,225
  • 13
  • 20
0

There are several solutions. The easiest and self-describing is based on spring.config.location property. @TestPropertySource has highest precedence, so the property goes there.

Following example demonstrates minimal Spring Boot test:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(initializers = {ConfigDataApplicationContextInitializer.class})
@TestPropertySource(properties = {"spring.config.location=classpath:debug.yml"})
@EnableConfigurationProperties({DataSourceProperties.class})
public class MyTest {
    @Autowired DataSourceProperties dbProps;

    @Test
    public void test() {
        System.out.println(dbProps.getUrl());
    }
}

ConfigDataApplicationContextInitializer is hidden when you use @SpringBootTest. @EnableConfigurationProperties is here because we slicing execution environment to minimum (no auto-scanning!).

spring.config.location property overrides default search strategy, like classpath:application.yml - nice if you want a completely separated config!

spring.config.location allows to specify a list of locations with comma:

classpath:common.yml,file:./config/local.yml

Some control is possible with spring.config.name.

Another way is to use @PropertySource(factory = ...) for YAML files (because Spring Framework only supports XML & .properties config files):

@PropertySource(value = {"file:./local.yml"}, factory = MyTest.YamlPropertySourceFactory.class)
public class MyTest {

    public static class YamlPropertySourceFactory implements PropertySourceFactory {
        @Override
        public org.springframework.core.env.PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
            factory.setResources(resource.getResource());
            return new PropertiesPropertySource(resource.getResource().getFilename(), factory.getObject());
        }
    }
    ...
}
gavenkoa
  • 45,285
  • 19
  • 251
  • 303