Issue importing properties in test - spring-boot

In a #RunWith(SpringRunner.class) test, I want to import some properties defined in com/.../test/resources/application.yml but they are not well formatted. This issue does not replicate when running the application.
Configuration class:
#ConfigurationProperties("my.property")
#Component
#Data
public class MyConfig{
private List<String> val1;
private List<String> val2;
}
main/.../application.yml:
my.property.val1: [string1, string2, string3]
my.property.val2: [string1, string2]
test/.../application.yml (just the same content):
my.property.val1: [string1, string2, string3]
my.property.val2: [string1, string2]
The test class:
#RunWith(SpringRunner.class)
#TestPropertySource(locations = "classpath:application.yml")
#EnableConfigurationProperties(value = MyConfig.class)
public class MyTest {
#Autowired
private MyConfig myConfig;
When running the application, the object looks good (meaning that val1 is a list of string1, string2, string3), but when running the unit test, the [ and ] are also included. For example, val1 would be a list of the following Strings: [string1, string2, string3]
Basically, the issue is that the [ and ] characters are included (but this happens only on the testing part). Any ideas?

Can you try setting the test values as the following. Normal structure for array in yaml:
my.property.val1:
- string1
- string2
- string3
my.property.val2:
- string1
- string2

Related

changing configuration value in spring-boot app with values of parameterized test

I use this in my MyService.kt.
The useXml is read via application.yml and application-test.yml
#Service
class MyService (
#Value("\${something.use-xml}")
var useXml: Boolean = false
) {
if(useXml) {
// do this
} else {
// do that
}
....
}
this works as expected.
but for the testing I want to use both variation:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyFunctionalTest {
#Value("\${something.use-xml}")
var useXml: Boolean = false
//....
#ParameterizedTest
#ValueSource(booleans = [true,false])
fun `do a parameterized test`(useXML : Boolean) {
useXml = useXML // this is (of course) not setting the variations for `MyService.kt`
if (useXML) {
// test this
} else {
// test that
}
}
I want a possibility where I can set the variable for the application.yml file, so both variations are tested.
Is there any way of doing it?
Of course, it is possible. You need to inject the service that you are testing and set its useXml value. In your case, it'll look like this:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class MyServiceTest {
#Autowired
private lateinit var myService: MyService
#ParameterizedTest
#ValueSource(booleans = [true,false])
fun `do a parameterized test`(useXML : Boolean) {
myService.useXml = useXML // you have to set the value for the test case
Assertions.assertEquals(useXML, myService.useXml)
}
}
However, if I were you and have such a case, I'd go with two totally separate implementations of the same interface, if xml and the other format have something in common. In the end, you will have two classes (you need better naming for that):
MyServiceXml
MyServiceJson
Both this classes will be annotated will #ConditionalOnProperty("\${something.use-xml}") and they will be implementing the same interface.
That will be very easy to test, two different implementations, so two different test classes.

Spring Boot inject List of Maps from application.yml

I am trying to inject List of Maps from Spring Boot config but getting an empty List. How to inject this correctly?
cacheConfigs:
- cacheOne:
test: test1
- cacheTwo:
test: test2
- cacheThree:
test: test3
- cacheFor:
test: test4
#ConfigurationProperties(prefix = "cacheConfigs")
public List<Map<String, String>> getCacheConfigs() {
return new ArrayList<>();
}
This was a "new" for me. I got this working by making cacheConfigs one level deeper and the used the new top level name as the #ConfigurationProperties param. Like this:
cache-configs-map:
cacheConfigs:
- cacheOne:
test: test1
- cacheTwo:
test: test2
- cacheThree:
test: test3
- cacheFor:
test: test4
Now, your configuration class looks like this:
#Configuration
public class Config{
#NoArgsConstructor #AllArgsConstructor( staticName = "of" )
#Getter #Setter
public static class C{
private List<Map<String, String>> cacheConfigs;
}
#Bean
#ConfigurationProperties(prefix = "cache-configs-map")
public C getC() {
return new C();
}
}

Spring Boot Kotlin - Injecting a map from a YAML file

test.yml (location: resources/properties/)
edit:
field1: test
field2: test
field3: test
field4: test
PropertyConfig.kt
#Configuration
#PropertySource("classpath:properties/test.yml")
class PropertyConfig {
#Bean
#ConfigurationProperties(prefix = "edit")
fun testProperty() = mutableMapOf<String, String>()
}
#Service
class EditService(
private val testProperty: Map<String, String>
) {
fun print() {
println(testProperty) // empty
}
}
I want to receive the values below edit as a map.
I tried options for #ConfigurationProperties with prefix and value, but it doesn't work.
If I use properties file, it works well, but not yml file.
What am I missing? Thanks.
kotlinVersion = '1.6'; springBootVersion = '2.6.1'
You are missing the #ContructorBinding annotation (required as of Spring Boot 2.2.0). Please see this answer:
#ConstructorBinding
#ConfigurationProperties("")
data class PropertyConfig(
val edit: Map<String,String>
)
If you wanna use a non-standard yml file (not called application.yml or derivate), like in the example you provided, then you need to add also the #PropertySource annotation to your Configuration data class.
#ConstructorBinding
#ConfigurationProperties("")
#PropertySource(value = "classpath:test.yml")
data class PropertyConfig(
val edit: Map<String,String>
)
Something like this (without #Bean):
#ConfigurationProperties(prefix = "") // blank since "edit:" is root element
#ConstructorBinding
data class EditProperties(
val edit: Map<String, String> // property name must match to relevant root element in YAML
)
#Service
class EditService(private val properties: EditProperties) {
fun print() {
println(properties.edit)
}
}
Output:
{field1=test, field2=test, field3=test, field4=test}

How to interpolate property values provided by custom PropertySource in Spring Boot?

I have my custom FooPropertySources that extends EnumerablePropertySource. I add all of these in the #Configuration class to the ConfigurableEnvironment and they are correctly picked up be application and all the values are resolved.
However, if some values contain placeholders, they're not being interpolated. I thought I should use PropertySourcesPlaceholderConfigurer to solve that problem, but it seems like this configurer is meant to deal with placeholders in beans, rather than in property sources.
So far I tried this:
#Configuration
#ConditionalOnProperty("foo.config.import")
open class FooConfiguration {
#Autowired
private lateinit var env: ConfigurableEnvironment;
#Value("\${foo.config.import}")
private lateinit var locationSpecifier: String;
#PostConstruct
private fun initialize() {
val placeholderConfigurer = PropertySourcePlaceholderConfigurer();
val beanFactory = DefaultListableBeanFactory();
this.resolvePropertySources(this.parseLocationSpecifier())
.forEach(this.env.propertySources::addFirst);
placeholderConfigurer.setEnvironment(this.env);
placeholderConfigurer.postProcessBeanFactory(beanFactory);
}
internal fun resolvePropertySources(path: Path): Set<FooPropertySource> {
//...
return ...;
}
internal fun parseLocationSpecifier(): Path {
//...
return path;
}
}
Now, if an instance of FooPropertySource contains these properties:
firstname = John
lastname = Doe
fullname = ${firstname} ${lastname}
I'd like, in the end, when my application calls to env.getProperty("fullname") it will get the string "John Doe", rather than "${firstname} ${lastname}".
Any hopes to resolve that problem? I'm struggling with it for third day already… :-(
I guess you could create an extension function
fun ConfigurableEnvironment.fullname() = "${getProperty("firstname")} ${getProperty("lastname")}"

How to write a Spring Boot YAML configuration with nested JSON?

I have been using Spring Boot applications with YAML for the external configuration, and this has been working very well so far. A toy example:
#Component
#ConfigurationProperties
class MyConfig {
String aaa;
Foo foo;
static class Foo {
String bar;
}
}
And then a yaml file with the following properties:
aaa: hello
foo.bar: world
My problem is that I really need to add a JsonObject into my configuration. I first tried adding it as a field in the MyConfig class, and then writing the following YAML file which I believe is syntactically valid:
aaa: hello
from:
{
"first_initial": "D",
"last_initial": "E"
}
foo.bar: world
Spring threw the following error with that: Cannot access indexed value in property referenced...
I finally resorted to making the value a plain string instead and using the > folding tag to put it in the YAML, but this means I have to manually parse the string into a JsonObject in my code.
Anyone have an idea for how to do this?
This should work:
#Component
#ConfigurationProperties
class MyConfig {
String aaa;
Foo foo;
String from;
static class Foo {
String bar;
}
// ... getters setters
public JsonObject getFromAsJson() {
// create object from "this.from"
}
}
aaa: hello
foo:
bar: world
from: |
{
"first_initial": "D",
"last_initial": "E"
}
and this:
aaa: hello
foo:
bar: world
from: "{\"first_initial\": \"D\", \"last_initial\": \"E\"}"
The first version would preserve line breaks.

Resources