Spring Boot Test Properties Reduction

By | August 31, 2021

In this article I will show how to reduce the set of application configuration properties available to a Spring Boot test. The reason for me wanting to do this is to be able to write tests for code that relies on a dynamic set of configuration properties.

Hiding properties from a test.

The source-code of the completed example project can be found here.

Create Project

I’ve created an example project using Spring Initializr. Click this link and, in the browser, press the Generate button to download a skeleton Spring Boot project. Open the project in the IDE of your choice.

Properties File

Open the application properties file located in src/main/resources and add properties so it looks like this:

test.propertylist.property1=One
test.propertylist.property2=Two
test.propertylist.property3=Three
test.propertylist.property4=Four

The above properties are a variable-size list of properties which contain four items in its current incarnation. Adding another property to the list may look like this (do not add the fifth property to the file):

test.propertylist.property1=One
test.propertylist.property2=Two
test.propertylist.property3=Three
test.propertylist.property4=Four
test.propertylist.property5=Five

Properties Container

The following class acts as a container for the properties list introduced above.

package se.ivankrizsan.spring.testproperties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * Custom properties container that accepts a variable number of properties.
 *
 * @author Ivan Krizsan
 */
@Component
@ConfigurationProperties(prefix = "test")
public class TestProperties {
    /* Constant(s): */

    /* Instance variable(s): */
    protected Map<String, String> propertylist = new HashMap<>();

    public Map<String, String> getPropertylist() {
        return propertylist;
    }

    public void setPropertylist(Map<String, String> propertylist) {
        this.propertylist = propertylist;
    }
}

Note that:

  • The TestProperties class is annotated with the @Component annotation.
    This is in order for a Spring bean to be auto-discovered and created.
  • The TestProperties class is annotated with the @ConfigurationProperties annotation.
    In addition, the prefix is set to “test” in the annotation. This will bind all properties which names start with “test” to a Spring bean of the annotated type.
    If the name of a property is “test.myproperty” then Spring will search for a method with the name “setMyproperty” and, if found, invoke it with the property value as parameter.
  • There is an instance variable named “propertylist” of the type Map<String, String>.
    All properties starting with “test.propertylist” will be inserted into this map with the remaining name of the property as key and the property value as value.
    Example: For the property test.propertylist.property1=One the key in the map will be “property1” and the value will be “One”.

First Version

First a test is devised that examines the properties container into which the application properties are loaded. In this first version these are the properties in src/main/resources.

Test Implementation

The first version of the test-class in which the properties list is inspected looks like this:

package se.ivankrizsan.spring.testproperties;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Map;

/**
 * Tests variable-sized properties lists and the reduction of properties in that list
 * in tests.
 *
 * @author Ivan Krizsan
 */
@SpringBootTest
public class ProfilePropertiesTest {
    /* Constant(s): */
    private static final Logger LOGGER = LoggerFactory.getLogger(ProfilePropertiesTest.class);
    protected static final int EXPECTED_PROPERTIES_COUNT = 4;

    /* Instance variable(s): */
    @Autowired
    protected TestProperties testProperties;

    /**
     * Tests obtaining properties.
     * Expected result:
     * The properties list should contain the expected number of properties.
     */
    @Test
    public void populatePropertiesListTest() {
        /* List the properties in the list. */
        LOGGER.info("********** TestProperties:");
        for (Map.Entry<String, String> entry : testProperties.getPropertylist().entrySet()) {
            LOGGER.info("A property from the TestProperties: {} = {}",
                entry.getKey(), entry.getValue());
        }

        /* Verify the number of properties. */
        Assertions.assertEquals(
            EXPECTED_PROPERTIES_COUNT,
            testProperties.getPropertylist().size(),
            "The properties list should contain the expected number of properties");
    }
}

The test in its current incarnation is very simple; an instance of the properties container, the TestProperties class, is injected into the test class. The size of the properties list is retrieved and checked. Apart from the actual check, the list of properties is logged to the console. This is purely for the sake of this example and not something that you’d normally find in a test.

Test Run

Running the first version of the test produces the following console log:

2021-08-23 23:19:34.855  INFO 2524 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : Started ProfilePropertiesTest in 0.656 seconds (JVM running for 1.793)
2021-08-23 23:19:35.103  INFO 2524 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : ********** TestProperties:
2021-08-23 23:19:35.103  INFO 2524 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property2 = Two
2021-08-23 23:19:35.105  INFO 2524 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property1 = One
2021-08-23 23:19:35.105  INFO 2524 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property4 = Four
2021-08-23 23:19:35.105  INFO 2524 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property3 = Three

Only the logs from the test method have been reproduced above. We can see that the properties in the list of the properties container does indeed match the properties found in the application.properties file in src/main/resources and that there are a total of four properties.

Second Version

In the second version I will attempt to use the @TestPropertySource annotation on the test-class and a custom property file.

Test Implementation

The implementation of the test-class is identical except for the added @TestPropertySource annotation on the test-class and, of course, the corresponding import.

package se.ivankrizsan.spring.testproperties;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import java.util.Map;

/**
 * Tests variable-sized properties lists and the reduction of properties in that list
 * in tests.
 *
 * @author Ivan Krizsan
 */
@SpringBootTest
@TestPropertySource
public class ProfilePropertiesTest {
    // Rest of the class is identical to the previous version.
    // ...

Custom Properties File

If we would run the test now, it would fail since there is no properties file available for the test in question. When using the @TestPropertySource annotation without any additional values supplied in the annotation, an attempt will be made to use a properties file with the name of the test-class appended with “.properties” located in the same package as the test. Create such a property file using the following steps:

  • Create a directory named “resources” in the src/test directory in the project if it does not already exist.
  • Create the directories in the se/ivankrizsan/spring/testproperties hierarchy in the src/test/resources directory.
    This means first creating a directory named “se” in the src/test/resources directory, then create a directory named “ivankrizsan” in the se directory you just created etc.
  • In the directory src/test/resources/se/ivankrizsan/spring/testproperties create a file named ProfilePropertiesTest.properties with the following contents:
test.propertylist.property1=ProfileTestOne
test.propertylist.property2=ProfileTestTwo

Note that there are only two properties in this properties file. The names of the properties are identical to the names of two of the properties in the application.properties file in src/main/resources but the values are different.

Test Run

Running the second version of the test produces the following console log:

2021-08-26 21:29:52.722  INFO 4251 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : Started ProfilePropertiesTest in 0.792 seconds (JVM running for 1.771)
2021-08-26 21:29:52.989  INFO 4251 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : ********** TestProperties:
2021-08-26 21:29:52.990  INFO 4251 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property2 = ProfileTestTwo
2021-08-26 21:29:52.991  INFO 4251 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property1 = ProfileTestOne
2021-08-26 21:29:52.992  INFO 4251 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property4 = Four
2021-08-26 21:29:52.992  INFO 4251 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property3 = Three

As earlier, only the logs from the test method have been reproduced above.

Note that:

  • There are four properties listed in the console log.
  • The name of the properties listed in the console log are the same as when running the first version of the test above.
  • The value of the property property1 is ProfileTestOne.
    This is the value from the custom properties file created above and not the value from the application.properties file.
  • The value of the property property2 is ProfileTestTwo.
    Again, this is the value from the custom properties file created above.
  • The value of the property3 and property4 properties are Three and Four respectively.
    These are the values from the application.properties file.
    None of these properties are defined in the custom properties file

There is thus a mix of properties from the application.properties file located in src/main/resources and from the ProfilePropertiesTest.properties file located in src/test/resources/se/ivankrizsan/spring/testproperties. This is not what I wanted when I set out to write my test and not the behaviour I expected to see when using the @TestPropertySource annotation. Instead, I would have expected that there would have been only two properties logged, namely property1 and property2. The values of the properties property1 and property2 were however the expected and so at least the second version is a partial success.

Third Version

In the third version, I will attempt to correct what, as far as I am concerned, is an issue with properties from the application.properties file being visible to the test if there is not a duplicate property defined in the custom properties file of the @TestPropertySource annotated test-class.

Updated Custom Properties File

To replace the properties read from the application.properties file in src/main/resources, the spring.config.location configuration property can be used. Since the intention is for the application.properties file not to be read, the property can obviously not be located in that file. Instead the property will be added to the custom properties file created in the second version earlier.

The new version of the custom properties file, still named ProfilePropertiesTest.properties, looks like this:

spring.config.location=classpath:/custom-properties/

test.propertylist.property1=ProfileTestOne
test.propertylist.property2=ProfileTestTwo

The spring.config.location property has the value “classpath:/custom-properties/” which will result in application properties being searched for in the custom-properties package on the classpath.

Empty Properties File

The properties file that is to replace the application.properties file in src/main/resources is to be empty in order to allow for tests to decide exactly which properties that are to be available.

  • In the src/test/resources, create a directory named “custom-properties”.
  • In the custom-properties directory, create an empty file named “application.properties”.

Test Run

Running the test with the third version modifications, it fails with the following message:

The properties list should contain the expected number of properties ==> expected: <4> but was: <2>

This is actually a success, since two properties in the properties list is the original goal of the article. The failure is caused by faulty expectations. To correct the expected number of properties, modify the constant EXPECTED_PROPERTIES_COUNT in the ProfilePropertiesTest class to look like this:

protected static final int EXPECTED_PROPERTIES_COUNT = 2;

Running the test again, it should now pass and produce the following output to the console log:

2021-08-28 21:08:51.345  INFO 3945 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : Started ProfilePropertiesTest in 0.807 seconds (JVM running for 1.811)
2021-08-28 21:08:51.609  INFO 3945 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : ********** TestProperties:
2021-08-28 21:08:51.609  INFO 3945 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property2 = ProfileTestTwo
2021-08-28 21:08:51.610  INFO 3945 --- [    Test worker] s.i.s.t.ProfilePropertiesTest            : A property from the TestProperties: property1 = ProfileTestOne

Note that:

  • There are only two properties logged to the console.
  • The properties logged to the console are identical to those contained in the custom properties file ProfilePropertiesTest.properties.
    Both the keys and values match.
  • None of the properties from the application.properties file in src/main/resources have been logged.
    Thus

Final Words

With two additional properties files, one of which is empty, and the @TestPropertySource annotation tests can now have complete control over the properties available during the execution of a test and even reduce the number of properties visible to the test.

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *