This Spring Boot code demonstrates several ways of using a type-safe configuration class during unit testing with properties set by various means.
The application itself does nothing useful. If you run it, the LogIdentifierRunner will simply
log the value of app.identifier that was loaded into the properties bean AppProperties that
was autowired into the service bean UsesAppPropertiesService.
This in itself demonstrates a feature of Spring Boot where it can externalize configuration
from several places including an application.yml (or application.properties) located in the
application's class path. In Maven/Gradle terms, that's simply the file
src/main/resources/application.yml since the build will carry that over to the application's
build artifact.
To give some background, the focal point of this repository is a type-safe properties class,
AppProperties, which contains:
@ConfigurationProperties("app")
@Component
@Data
public class AppProperties {
/**
* The identifier our application wants.
*/
String identifier = "default";
}There's a lot going on in this one little, single-field class:
-
@ConfigurationProperties("app")marks this as a type-safe configuration properties class that will be bound to properties with theappprefix -
@Componentmarks this class to be picked up by component scanning and registered into the Spring application context -
@Datais a Lombok annotation that gets processed during compilation to make the data fields private and inject getter/setters. It also injects a few other things -
The field
identifieris what our application wants to access. It will become bound to a sub-property named for the field,identifier, and that binding will ensure it is a validString. It is declared with a default literal value,"default". That default value, along with the field's type and name, get picked up by configuration processing and displayed by your IDE, etc. -
The field description in the javadoc
/** ... */also gets picked up by configuration processing and is displayed in this IntelliJ quick docs tooltip:
The bulk of this repository is actually the unit tests located in src/main/test. The following
sections describe each strategy used to populate AppProperties during a unit test.
@RunWith(SpringRunner.class)
@SpringBootTestThis is the easiest since a @SpringBootTest effectively initiates a full application context
load along with all the externalized configuration loading and property binding.
The property value gets picked up from the application.yml in the class path, which originates
from src/main/resources.
@RunWith(SpringRunner.class)
@SpringBootTest(
properties = "app.identifier=inline"
)This one overrides the value loaded from the classpath's application.yml with an inline property
value.
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
UsesAppPropertiesService.class
})
@EnableConfigurationProperties(AppProperties.class)As discused in the Spring Boot docs, sometimes you want and sometimes should test with a purposeful slice of your application's context. Unfortunately it takes a little more work to fine tune our test's context:
- The
@ContextConfigurationbrings in our service to be "tested" - The
@EnableConfigurationPropertiesactivates the configuration binding of our specific class and also registers it into the application context. In this case, I could have just addedAppPropertiesto the context configuration'sclasses; however, I find@EnableConfigurationPropertiesmore explicit and translates better to the other options, below.
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
UsesAppPropertiesService.class
}, initializers = {
ConfigFileApplicationContextInitializer.class
})
@EnableConfigurationProperties(AppProperties.class)This one better approximates (and verifies) what the final application will do by loading the
externalized configuration from the main application.yml. The addition of the
ConfigFileApplicationContextInitializer
in the @ContextConfiguration is what activated that behavior.
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
UsesAppPropertiesService.class
})
@EnableConfigurationProperties(AppProperties.class)
@TestPropertySourceThis one uses a feature provided by Spring Core's test facilities, which is the
@TestPropertySource.
This default use of the annotation is especially useful since you can alter the properties used for
each test suite. It looks for a .properties file named for the test class and the package/directory
containing it. In this repository, that file is me/itzg/trytestpropertiesloading/MinimalContextPropSourceTest.properties,
which is located in src/test/resources.
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
UsesAppPropertiesService.class
})
@EnableConfigurationProperties(AppProperties.class)
@TestPropertySource(properties = "app.identifier=inline")This one is a slight variation on the previous, replacing the properties file with the
properties set inline using the properties of @TestPropertySource. Note how this approach is
consistent with the properties of @SpringBootTest used above.
