Should I mock Spring Cloud Config server properties during tests? - java

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"})

Related

Junit 5 mockito unable to read properties file annotated with #Value in spring boot application

I am writing a junit test cases for one of component in spring boot application. That component is having #Value annotation and reading value from property file.
When I am running my Junit 5 (mockito) and controls goes to the component; the value is null.
What I have tried:
I used
#ExtendWith(SpringRunner) and changed #injectMocks to #Autowired and #mock to #MockBeans, but that is not I want (As its became integration test.)
Junit class:
#ExtendWith(MockitoExtension.class)
public class ItemMessageProcessorTest {
private static final String VALUE1 = "Value 1";
private static final String VALUE2 = "Value 2";
private static final String VALUE3 = "Value 3";
#InjectMocks
private MyComponent component;
Component class:
#Slf4j
#Component
public class MyComponent {
#Value("${my-val.second-val.final-val}")
private String myValue;
This myValue is being used here in the same component class:
public void myMethod(){
myObject.setMyValue(Integer.parseInt(myValue));
}
What I was looking for is something like:
If I can by any chance mock the parseInt, or load the values from test class itself. Any lead would be a great help.
Note: I can't change anything in the component class.
You can just use Spring reflection utills method for setting the field value with #Value for unit test:
org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
I would go for constructor injection in this case:
#Slf4j
#Component
public class MyComponent {
private final String myValue;
MyComponent(#Value("${my-val.second-val.final-val}" String myValue)) {
this.myValue = myValue;
}
}
Values from application.properties are loaded into Spring Application Context in these cases:
when application is running,
when Spring Integration tests are runnign.
In case of Unit tests properties are not loaded.
If you'd have a constructor injection you could set a value for tests and pass it to the constructor.
Most important thing, you don't want to load the entire Spring Application context to bind the properties automatically as this would hugely slow down the execution of the unit tests. Therefore, you cannot use #ExtendWith(SpringRunner).
You can make use of ReflectionTestUtils along with #ExtendWith(MockitoExtension.class) to bind the properties of MyComponent class.
org.springframework.test.util.ReflectionTestUtils.setField(MyComponent.class, "myValue", 1);

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

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

Junit 5 with test application.properties

I am learning Junit 5 and test cases.
I am using spring boot version '2.2.6.RELEASE and JUnit 5,
in my application, I have a method that processes based on the boolean flag from property file.
\src\main\resources\application.properties
#data base connection properties
spring.app.datasource.url=jdbc:mysql://localhost:3306/student_db
spring.app.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.app.datasource.username=root
spring.datasource.password=root
spring.app.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
#additional properties
spring.property.name=shrikant
spring.property.enable=false
database connection properties are used to create the database connection
Datasource.java
#Value("${spring.app.datasource.url}")
private String url;
#Value("${spring.app.datasource.driver-class-name}")
private String className;
#Value("${spring.app.datasource.username}")
private String userName;
#Value("${spring.datasource.password}")
private String password;
#Value("${spring.app.jpa.properties.hibernate.dialect}")
private String dialect;
controller class
#RestController
public class Controller {
#Value("${spring.property.name}")
private String name;
#Value("${spring.property.enable}")
private boolean status;
public void validateObject(String surName) {
if (status) { # if this flag is true then only process
System.out.println("name= " + name);
System.out.println("surName= " + surName);
}
}
ControllerTest.java
#SpringBootTest
class ControllerTest {
#Autowired
private Controller controller;
#Test
void show() {
controller.validateObject("sharma");
}
by default the flag is false, so every time test case runs it never processes the object.
so I tried to create aplication.properties in the test folder
\src\test\resources\application.properties
spring.property.name=vishal
spring.property.enable=true
but now it's giving me an error that
Could not resolve placeholder 'spring.app.datasource.url'
but I don't want to provide DB connection URL, I am not connecting to the database while testing.
Q1 - how to change the value of properties file for test case only.
Q2 - is it mandatory to provide all the keys of \src\main\resources\application.properties is \src\test\resources\application.properties?
I am new in test case, so little explained answers would be welcomed.
Update:-
I found that
#SpringBootTest
#TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"})
class ControllerTest {
will solve the issue temporarily, by providing keys along with values, but I have a lot of keys, which cannot be provided in such a way.
If you use #SpringBootTest then your test will load the whole Spring context. This means it will create all your beans and try to wire them together. If you inject property values to your beans, you have to specify them all for such tests as otherwise, you won't be able to boot the application.
What might help you in such a situation is to use test annotations like #WebMvcTest or #DataJpaTest to focus on testing just slices of your application. Using #WebMvcTest you'll get an application context just containing controllers and everything related to your web layer. Other beans (like service classes) can be mocked with #MockedBean.
Next, for testing business logic in service classes try not to use #SpringBootTest and rather rely on plain JUnit 5 and Mockito to verify your code.
You still might want to have some integration tests that use #SpringBootTest to make sure everything is working together. In such case, you can hard code any static property inside application.properties in src/test/resources/ or using the annotation like you already did: #TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"}).
When it comes to providing a database, you can either configure an embedded database (which I would try to avoid) or use the same database as in production. Testcontainers helps you a lot when it comes to providing external infrastructure for your tests like a database.
An example setup with Spring Boot >= 2.2.6 and JUnit might look like the following:
#Testcontainers
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
#Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withPassword("password")
.withUsername("username");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
}
#Test
public void contextLoads() {
}
}
For Junit5, annotate your test class with path to application.properties in your src/main/resources:
#ExtendWith(SpringExtension.class)
#TestPropertySource("classpath:application.properties")
public class MyTest {
#Value("${property.name}")
private int myProperty;
tests...
}
property gets loaded

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.

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