Fail to inject #Value in tests with Spring, Spock & Groovy - java

I have problems with injection #Value('${mybean.secret}') property into my bean during tests in Spock with Spring Boot & Groovy.
I have a very simple test class MyBeanTest
#ContextConfiguration(classes = [
MyAppConfig
])
#PropertySource([
"classpath:context/default-properties.yml"
])
class MyBeanTest extends Specification {
#Autowired
MyBean myBean
def "should populate properties "() {
expect:
myBean.secretProperty == "iCantTellYou"
}
}
And MyAppConfig.groovy as this:
#Configuration
class MyAppConfig {
#Bean
MyBean credential(#Value('${mybean.secret}') String secret) {
return new MyBean(secret)
}
}
When I run tests the value that is injected into secret is simply ${mybean.secret}.
The real value is not injected from properties file I enclose on test specification.
I'm using single-quote on #Value because of Groovy. Double quote with $ sign makes it being processed by groovy GString mechanism.
However, the problem doesn't occur on regular application run.
If I start application and put the breakpoint on the MyAppConfig#credential method the secret value is correctly read from the properties file, which are configured as follow:
#Configuration
#PropertySource(["classpath:context/default-properties.yml"])
class PropertiesConfig {
}
When I specify property from hand like this:
#TestPropertySource(properties = [
"mybean.secret=xyz"
])
class MyBeanTest extends Specification {
It works. The property is read. But it's not my goal, cause there's much more properties in the project and it would become cumbersone to define them everywhere from hand.
Can you spot the problem I am missing in this code?

The missing puzzle was YamlPropertySourceFactory.
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);
}
}
I was using yml properties files with nested properties:
mybean:
secret: xyz
And Spring was not loading them correctly.
I had to update #PropertySource annotation as well as follow:
#Configuration
#PropertySource(
value = ["classpath:context/default-properties.yml"],
factory = YamlPropertySourceFactory.class
)
class PropertiesConfig {
}
Now it works like a charm 😏.
I learned this on Baeldung website => https://www.baeldung.com/spring-yaml-propertysource

Related

How to check Spring profile explicitly?

I would like to check that my application runs only under given set of allowed profiles.
I would write
#Autowire
Environment environment;
#PostConstruct
public void checkAndReportProfiles() {
// checks
... environment.getActiveProfiles()
but the problem is that this runs after all beans were attempted to initialize and failed if profiles were incorrect.
I want to check profiles explicitly and fail with explicit message.
Is it possible to do this?
Option 1: you can create a bean which performes the checks for you.
#Component
public class TestBean {
public TestBean() {
throw new BeanInitializationException("error message");
}
}
to execute this before certain problematic beans you can annotate theese beans with #DependsOn
But now you have a bean with no purpose hanging around.
Option 2:
Perform the checks in a configration class.
To get access to the spring evironment or configuration properties, you can inject them via constructor.
#Configuration
public class TestConfiguration {
public TestConfiguration(Environment environment, #Value("${some.key}") String property) {
String[] profiles = environment.getActiveProfiles();
// perform tests here
throw new BeanInitializationException("error message");
}
}
Option 3: Create beans programatically and check before the bean is created.
#Configuration
public class TestConfiguration {
#Value("${some.key}")
String property;
private final Environment environment;
public TestConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
public SomeBean someBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
return new SomeBean();
}
}
Option 4: Perform test in the constructor of that particular bean. The constructor is called first.
#Component
public class SomeBean {
public SomeBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
}
}
Option 3 is the cleanest solution in my opinion, but also the most difficult to implement. Especially if you are using constructor based injections.
You can try this.
Create a class for keeping the association profile/error
public enum ErrorProfile {
default("This is the error num 1"),
dev("This is the error num 2"),
test("This is the error num 3"),
prod("This is the error num 4");
private String profile;
private String message;
ErrorProfile(String profile, String message){
this.profile = profile;
this.profile = message;
}
}
Then create a profile checker that execute at the lunch of your spring project during the context loading.
Java provides a valueOf(String) method for all enum types.
Thus, we can always get an enum value based on the declared name. This means that, after we get the value of the profile we can retrieve the message code with:
ErrorProfile.valueOf("prod")
Then it will be enough to print it in the Logs, Exception or whatever you need.
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
#Slf4j
#Configuration
public class PropertiesLogger {
#Value("${spring.profiles.active}")
private String activeProfile;
#PostConstruct
public void printProperties() {
log.debug("Loading application-context profiles. Active profile found: {} [message: {}]",
activeProfile,
ErrorProfile.valueOf(activeProfile));
// and in this case (cause you want to rise an exception with that message)
throw new RuntimeException(ErrorProfile.valueOf(activeProfile))
}
}
To explicitly check the Spring profile, you can use the following approach:
1)Set the active profile via the --spring.profiles.active application launch option:java -jar myapp.jar --spring.profiles.active=production
2)Set the active profile via the environment:
export SPRING_PROFILES_ACTIVE=productionjava -jar myapp.jar
3)Set the active profile via an application.properties or application.yml file:
# application.properties spring.profiles.active=production
# application.yml spring: profiles: active: production

Loading properties file from yml as Factory Bean in JUNIT

I have list of properties in my yml for eachplatform tv and android
i have created one factory class where we provide the platform and you will get the properties , using the referring link "https://andrewzc.com/loading-configuration-properties-in-spring-boot-test/" .
but on running the test the property object is coming as null . How to load properties in junit from properties file ?
#Component
public class MasterPropertiesFactory {
#Autowired
private TvProperties tv ;
#Autowired
private AndroidProperties android ;
public MasterPropertiesClass getProperty(String platform){
if(platform.equals("tv")){
return tv;
}else if(platform.equals("android")){
return android;
}
}
}
Now i am writing a junit to read these properties but on running test getPaginatedContent it is coming as null .
//PageTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {TestApp.class ,MasterPropertiesFactory.class }, initializers = ConfigFileApplicationContextInitializer.class)
class PageTest {
#Autowired
MasterPropertiesFactory masterPropertiesFactory;
#Test
void getPaginatedContent() {
Assert.assertNotNull(masterPropertiesFactory);
}
}
// TestApp.java
#EnableAutoConfiguration
#EnableConfigurationProperties(value = {MasterPropertiesFactory.class })
public class TestApp {}
There're several points you should fix in your code:
There should be Configuration class for creating TvProperties and AndroidProperties
Either you should use that class in PageTest - ContextConfigurationor you should declare custom Configuration class for the test, to load the properties.
#ContextConfiguration(classes = {TestApp.class ,MasterPropertiesFactory.class }is not good idea, because, it should contain information about Configuration classes.
For #Autowired
MasterPropertiesFactory masterPropertiesFactory; somewhere you should create bean for that (preferred way is in test Conifugration)

Overriding #Value in Integration Test

For one of my Spring beans(say Application class), I'm fetching the value of a property(my.property.flag=true/false) from a properties file(prop.properties) using #Value annotation. That works perfectly fine.
I need to write an integration test(say ApplicationIt class) where I need to test with both the values of the property i.e. for both true and false.
In my properties file, the value of the property is set to true. Is it possible to set the value dynamically to false from my Integration test?
For Example,
prop.properties:
my.property.flag=true
Application class file:
#Component
class Application {
//This value is fetched from properties file
//the value is set to true.
#Value(${my.property.flag})
private String isTrue;
......
..........
}
Integration Test:
class ApplicationIT {
//how can I set the value of isTrue here to false?
}
You can specify test properties on the test class as follows:
#RunWith(SpringRunner.class)
#TestPropertySource(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
public class MyTest {
Since Spring has a whole hierarchy of property overrides, this works pretty well, the downside being you need separate test classes for different values. If you're using Spring Boot, there's another annotation that provides the same functionality but also has more options for configuring your test environment. Example:
#SpringBootTest(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
Again, you will need separate test classes to handle hard-coded test properties.
I was bugged with this for a while and found this neat way to override the properties. It is quite useful if you need some programmatic initialization of the application context such as registering property sources like in that case but not only. The following approach uses ContextConfiguration's initializers.
example for Spring Boot 1.5.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(ctx,
"spring.redis.host=" + redis.getContainerIpAddress(),
"spring.redis.port=" + redis.getMappedPort(REDIS_PORT));
}
}
}
example for Spring Boot 2.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertyValues.of(
"spring.redis.host:" + redis.getContainerIpAddress(),
"spring.redis.port:" + redis.getMappedPort(REDIS_PORT))
.applyTo(ctx);
}
}
}
I want to mention good old reflection way. You can use spring provided utility class for it after you wired in your component:
ReflectionTestUtils.setField(component, "isTrue", true)
You can change it to any value you want in consequent tests
Preferably, use constructor injection instead of field injection:
#Component
class Application {
Application(#Value("${my.property.flag}") boolean flag) {
...
}
}
This makes using mocks or test values as simple as passing an argument.

Should I mock Spring Cloud Config server properties during tests?

How do I test a service that has properties from spring cloud config server injected into it as a dependency?
-Do I simply create my own properties during testing using the new keyword?(new ExampleProperties())
Or do I have to use spring and create some kind of test properties and use profiles to tell which properties to use?
Or should I just let spring call the spring cloud config server during testing?
My service looks like the one below:
#Service
class Testing {
private final ExampleProperties exampleProperties
Testing(ExampleProperties exampleProperties) {
this.exampleProperties = exampleProperties
}
String methodIWantToTest() {
return exampleProperties.test.greeting + ' bla!'
}
}
My project makes a call to a spring cloud config server during start up to get properties, this is enabled by having the following on the bootstrap.properties:
spring.cloud.config.uri=http://12.345.67.89:8888
I have a configuration that looks like the one below:
#Component
#ConfigurationProperties
class ExampleProperties {
private String foo
private int bar
private final Test test = new Test()
//getters and setters
static class Test {
private String greeting
//getters and setters
}
}
The properties file looks like this:
foo=hello
bar=15
test.greeting=Hello world!
You can use #TestPropertySource annotation to fake properties during test:
#ContextConfiguration
#TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
public class MyIntegrationTests {
// class body...
}
For Unit test just simply mock Properties and use Mockito methods when(mockedProperties.getProperty(eq("propertyName")).thenReturn("mockPropertyValue") and it will be fine.
For Integration test all Spring context should be inited and work as regular app, in that case you dont need to mock your properties.
Another option is to use properties attribute of SpringBootTest annotation:
#SpringBootTest(properties = {"timezone=GMT", "port=4242"})

Can I manually load #ConfigurationProperties without the Spring AppContext?

Is there any way to load a class marked with #ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.
I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:
#ConfigurationProperties(prefix="my")
public class MySettings {
String property1;
File property2;
}
See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args
But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.
So I instead want to do something like:
MySettings mySettings = new MySettings();
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);
And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?
Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.
Possible or pipe dream?
Thanks for your help!
I had the same "issue".
Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.
Let's say we have the following yaml configuration file:
foo:
apis:
-
name: Happy Api
path: /happyApi.json?v=bar
-
name: Grumpy Api
path: /grumpyApi.json?v=grrr
and we have the following ConfigurationProperties:
#ConfigurationProperties(prefix = "foo")
public class ApisProperties {
private List<ApiPath> apis = Lists.newArrayList();
public ApisProperties() {
}
public List<ApiPath> getApis() {
return apis;
}
public static class ApiPath {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(final String aName) {
name = aName;
}
public String getPath() {
return path;
}
public void setPath(final String aPath) {
path = aPath;
}
}
}
Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:
private static ApisProperties apiProperties() {
try {
ClassPathResource resource;
resource = new ClassPathResource("/config/application.yml");
YamlPropertiesFactoryBean factoryBean;
factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setSingleton(true); // optional depends on your use-case
factoryBean.setResources(resource);
Properties properties;
properties = factoryBean.getObject();
MutablePropertySources propertySources;
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
ApisProperties apisProperties;
apisProperties = new ApisProperties();
PropertiesConfigurationFactory<ApisProperties> configurationFactory;
configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the #ConfigurationProperties
configurationFactory.bindPropertiesToTarget();
return apisProperties; // apiProperties are fed with the values defined in the application.yaml
} catch (BindException e) {
throw new IllegalArgumentException(e);
}
}
Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):
public class AppConfigTest {
private static AppConfig config;
#BeforeClass
public static void init() {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new ClassPathResource("application.yaml"));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
config = binder.bind("app", AppConfig.class).get(); // same prefix as #ConfigurationProperties
}
}
The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).
This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.
Spring Boot Binder API support for #Value Annotations
#Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).
import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get

Categories

Resources