Overriding #Value in Integration Test - java

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.

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

How to read variable from application.properties from method that is testing

I have class:
#Service
public class A {
#Value("${a.b.c}")
private String abc;
public void foo() {
sout(abc);
}
}
I Have test class:
#SpringBootTest
#SpringBootConfiguration
#RunWith(SpringRunner.class)
#TestPropertySource(locations = "classpath:application.yml")
public class TestA {
#Value("${a.b.c}")
private String abc;
#InjectMocks
private A a;
#Test
public void testFoo() {
this.a.foo();
}
}
When I debugging the test method testFoo(),
I see that variable abc is read from the application.yml file.
But,
inside the foo() method,
I see that the variable abc is null.
How can I set variable abc such that it is available in method foo() when I trying to test this method?
Step one is to answer this question: Am I unit testing the code in my class or am I integration testing the combination of Spring and some collection of code that includes my class?
If you are unit testing your code,
then it is not necessary to have Spring do its thing.
Instead,
you only need to instantiate your class,
set the values that Spring would have set for you,
execute the method you are testing,
then verify that your method executed correctly.
Here is your example unit test rewritten as I suggested:
public class TestA
{
private static final String VALUE_ABC = "VALUE_ABC";
private A classToTest;
#Test
public void testFoo()
{
classToTest.foo();
}
#Before
public void preTestSetup()
{
classToTest = new A();
ReflectionTestUtils.setField(
classToTest,
"abc",
VALUE_ABC)
}
}
Some Notes:
ReflectionTestUtils is part of Spring-test.
You don't need to use #InjectMocks because you have no mocks to inject.
I don't know what sout is, so I excluded it from the test. You should verify that the sout method was called with the correct value (in this case VALUE_ABC).
If you are just unit testing your code, you don't need Spring, which means that you don't need to use the #RunWith annotation.
You can try to overide the properties like that:
#TestPropertySource(locations = "location.properties",
properties = "a.b.c=123")
Example taken from here

Defining a spring active profile within a test use case

Using Spring 4, I've got the following test setup:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = JpaConfig.class)
#ActiveProfiles(resolver = TestResolver.class)
public class SimpleTest {
The TestResolver has been implemented as:
public class TestResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> aClass) {
String[] profiles = new String[1];
profiles[0] = "test";
return profiles;
}
}
JpaConfig has been annotated with PropertySource
#Configuration
#PropertySource("classpath:properties/application-${spring.profiles.active:dev}.properties")
#EnableJpaRepositories(basePackages={"com.my.namespace.repositories"})
public class JpaConfig {
Whenever I run the SimpleTest it tries to locate: properties/application-dev.properties while I expected it to be properties/application-test.properties.
What's I'm trying to accomplish here has been based on the following post: Spring integration tests with profile
I believe this is, actually, the issue you are facing. And in that same post you have an explanation from Dave Syer and a possible solution from another user. To follow Dave's advice, this would be a possible implementation of an ApplicationContextInitializer:
public class MyApplicationContextInitializer implements
ApplicationContextInitializer<GenericApplicationContext> {
public void initialize(GenericApplicationContext context) {
context.getEnvironment().getSystemProperties().put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "some_profile");
}
}
and on your test class:
#ContextConfiguration(classes = JpaConfig.class, initializers = MyApplicationContextInitializer.class)
But I would say that the suggested approach (with different .properties files loaded for different profiles) in that SO post is a more elegant approach.
I think you should change #PropertySource to:
#PropertySource("classpath:properties/application-${spring.profiles.active}.properties")
Also for simplicity (shouldn't have any effect on how the code runs) your #ActiveProfile could be
#ActiveProfiles("test")

Categories

Resources