My Junit is not picking up properties set in test properties file.
I get no error, but value returned from properties file is null
CLASS TO BE TESTED:
package com.abc.mysource.mypackage;
#Component
#ComponentScan
public class MyHelper {
#Autowired
#Qualifier("commonProperties")
private CommonProperties commonProperties;
public LocalDateTime method1ThatUsesCommonProperties(LocalDateTime startDateTime) throws Exception {
String customUserType = commonProperties.getUserType(); // Returns null if run as JUnit test
//Further processing
}
}
SUPPORTING COMPONENTS - BEANS & CONFIG CLASSES:
package com.abc.mysource.mypackage;
#Component
public class CommonProperties {
#Value("${myhelper.userType}")
private String userType;
public String getUserType() {
return userType;
}
public void setCalendarType(String userType) {
this.userType = userType;
}
}
CONFIG CLASS:
package com.abc.mysource.mypackage;
#Configuration
#ComponentScan(basePackages ="com.abc.mysource.mypackage.*")
#PropertySource("classpath:default.properties")
public class CommonConfig {}
default.properties under src/main/resources
myhelper.userType=PRIORITY
MY TEST CLASS:
package com.abc.mysource.mypackage.test;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes=MyHelper.class)
#TestPropertySource("classpath:default-test.properties")
#EnableConfigurationProperties
public class MyHelperTest {
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
#Autowired
private MyHelper myHelper;
#Test
public void testMethod1ThatUsesCommonProperties() {
myHelper.method1ThatUsesCommonProperties();
}
}
default-test.properties defined under /src/test/resources:
myhelper.userType=COMMON
NOTE:
I moved default-test.properties to /src/main/resources - commonProperties.getUserType() is null
I even used #TestPropertySource(properties = {"myhelper.userType=COMMON"}). Same result
NOTE 2:
I tried the solution on #TestPropertySource is not loading properties.
This solution requires me to create a duplicate bean called CommonProperties under src/test/java. But #MockBean fails when I do
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
Please do not mark a duplicate.
NOTE 3:
Mine is a spring, not a spring boot application.
MockBeans are suited if you don't need specific state. Usually this bean is "isolated" and every method call of this bean will have the same result. It is "isolated" -> the service that uses #Value annotation will not apply on this bean.
What you need is a "normal" bean, properly constructed and initialized. Please use #Autowired annotation and define another bean if needed, using a test profile.
Related
I'm trying to test a class that has the #ConfigurationProperties annotation but without loading the entire Spring context. I tried using only the JUnit5's features in order to do that but until now didn't succeed in that.
I'm using spring-boot-starter-parent v2.6.2 .
The class I'm testing :
#ConfigurationProperties("db.mongo")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class MongoProperties {
private String host;
private String db;
private String user;
private String password;
}
The application.yaml :
db:
mongo:
host: localhost
db: test
user: test-user
password: secret
My Test class :
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties({MongoProperties.class})
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Test
public void mongoPropertiesLoadedTest(){
assertNotNull(properties.getDb());
assertNotNull(properties.getHost());
assertNotNull(properties.getPassword());
assertNotNull(properties.getUser());
}
}
The MongoProperties bean is injected successfully, but all the values inside are null and the asserts fail.
Adding the #SpringBootTest solves the issue of the null values in the instance of the bean, but it also starts the whole spring context which is what I don't want.
But using #ExtendWith(SpringExtension.class) will also start the spring context. The difference is that it starts the context in a traditional way but #SpringBootTest starts it in a spring-boot way. So no matter you use which of them , it still requires to start the spring context.
If your concern is to minimise the number of beans required to be loaded into the spring context when using #SpringBootTest, you can actually configure a specified #Configuration like the following as by default #SpringBootTest will load all beans defined in your applications which may be too much for testing (see this for details) :
#SpringBootTest
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
If you really want to just use #ExtendWith(SpringExtension.class) , you will lose the spring-boot feature such as externalising configuration features which cause you cannot load properties from application.properties and cannot support loading properties from YAML file etc. You have to manually configure ConfigDataApplicationContextInitializer to enable such features :
#ExtendWith(SpringExtension.class)
#ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
You can consider to further use #SpringJUnitConfig to combine #ExtendWith(SpringExtension.class) and #ContextConfiguration together which gives you :
#SpringJUnitConfig(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
Actually both approaches do not have much differences in term of speed , so I prefer to just use #SpringBootTest for simplicity as it does not requires you to configure ConfigDataApplicationContextInitializer.
When I try to print the variable that I have autowired, it prints "null" instead of the value I set it to, "Example." I can't quite figure out what I'm missing
In my AppConfig class I have:
#Configuration
public class ApplicationConfiguration {
#Bean
public String tableName(){
return "Example";
}
}
In my other class, DAOMethods, that I want to autowire the variable in:
#Component
public class DAOMethods {
#Autowired
private String tableName;
public void print(){
System.out.println(tableName);
}
}
They exist in different packages; With AppConfig living in a config
folder and DAOMethods in client->dynamodb->util folder. Config and
Client are folders under the main->java folder
The added #Configuration annotation scans for the beans in the current and its subpackages. You need to explicitly tell the application to scan the required packages. So you can do:
#SpringBootApplication (scanBasePackages = {"config", "client"})
OR,
You need to keep the config and other classes which uses that config in same root package. You can put the config folder and client folder under same package, say com.application and then add the following in your main class:
#SpringBootApplication(scanBasePackages = "com.application")
Now run the application.
You problem have many solutions
F.e. you can create config-class with required set of parameters and next autowire it (Injecting values with #Value annotation from application configuration file is good practice):
#Component
public class CustomConfiguration {
#Value("${table.name}")
private String tableName;
#Value("${some.value}")
private Integer someValue;
public String getTableName() {
return tableName;
}
public Integer getsomeValue() {
return someValue;
}
}
And you application.properties will looks like:
some.value=1
table.name=Example
Or you can simply inject single value from configuration with #Value annotation
One of solutions is using bean name in #Value annotation:
#Configuration
public class ApplicationConfiguration {
#Bean
public String tableName(){
return "Example";
}
}
#Component
public class DAOMethods {
#Value(#{tableName})
private String tableName;
}
More examples you can see in this question: Autowire a string from Spring #Configuration class?
We are upgrading the spring boot version from 1.3.0.RELEASE to 2.3.12.RELEASE.
As per the old version, yml files were read using the following code snippet
#Configuration
#ConfigurationProperties(locations = "classpath:/config/myconf-source.yml")
public class MyConfigProperties {
private String configSource;
public String getConfigSource() {
return configSource;
}
public void setConfigSource(String configSource) {
this.configSource = configSource;
}
}
Config files in src/main/resources/config/
myconf-source.yml
news-source.yml
conf-mapping.yml
Content in myconf-source.yml
configSource: "TEST"
Corresponding Test Class
#ActiveProfiles("test")
#RunWith(SpringJUnit4ClassRunner.class)
#EnableAutoConfiguration
#SpringApplicationConfiguration(classes = SampleApplication.class)
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#ConfigurationProperties(locations = "classpath:**/config/**")
public class MyConfigPropertiesTest {
#Autowired
private MyConfigProperties myConfigProperties;
#Test
public void testMyConfigProperties() {
String config = myConfigProperties.getConfigSource();
Assert.assertEquals(config, "TEST");
}
}
After changing to the new version, it throws an error Cannot resolve method 'locations'.
If I remove locations attribute how spring will know the class MyConfigProperties has to read myconf-source.yml
Also while running the test class, NullPointerException is thrown as myConfigProperties.getConfigSource(); becomes null.
I have tried various solutions posted but no luck,
Can anyone suggest how to make it work?
Thanks
#Configuration should be used if in that class you define beans with #Bean.
If not then remove it from there.
Also #Configuration does not make this class a bean to be autowired in the test that you require it to be.
If you want MyConfigProperties to be available for autowiring then you also need
#EnableConfigurationProperties(MyConfigProperties.class). This will make sure that this class is available as a spring bean in the application context.
So it would be
#PropertySource("classpath:/config/myconf-source.yml")
#ConfigurationProperties()
#EnableConfigurationProperties(MyConfigProperties.class)
public class MyConfigProperties {
private String configSource;
public String getConfigSource() {
return configSource;
}
public void setConfigSource(String configSource) {
this.configSource = configSource;
}
}
You can use #PropertySource annotation to read the yml file , you can read the below article :
https://www.baeldung.com/properties-with-spring
I have two beans with the same signature. They are named in order to get the correct instance to the classes requesting them.
#Configuration
public class MyConfiguration {
#Bean("durationForX")
public Duration durationForX() {
return Duration.ofSeconds(1);
}
#Bean("durationForY")
public Duration durationForY() {
return Duration.ofSeconds(5);
}
}
and used as
#Component
public class MyService {
public MyService(
#Qualifier("durationForX") duration
) {
...
}
}
and similar for Y.
Now, I want to have mocks of the above beans autowired in an integration test. I've tried the following
#Configuration
#Profile("my-test-profile")
public class IntegrationTestConfiguration {
#Primary
#Bean("durationForX")
public Duration durationForXMock() {
return Duration.ofMillis(100);
}
#Primary
#Bean("durationForY")
public Duration durationForYMock() {
return Duration.ofMillis(500);
}
#Primary
#Bean
public AnotherService anotherService() {
// This one works as expected, probably because it is not a named bean
...
}
}
which, when running the integration tests, results in the error message
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'durationForX', defined in class path resource [com/../../MyConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/.../.../IntegrationTestConfiguration.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
I'm not auto-wiring the instances themselves in the integration tests, only one entry point for the application in order to call it.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {MyApp.class})
#ActiveProfiles("it")
class MyIntegrationTest {
#Autowired
GraphQLTestTemplate graphQL;
...
}
I'm not too keen on setting the bean override to true as I want to be in control of which beans are used where. I would expect mocking the named beans to follow the same pattern as the not named one, why is this? Any idea on workarounds?
I would recommend using the different profile for test, for example you can define the values in main application.yml for main application
application.yml
duration1:1
duration2:5
And then in read them in MyConfiguration class using #Value annotation
#Configuration
public class MyConfiguration {
#Value("${duration1})
private Integer duration1;
#Value("${duration2})
private Integer duration2;
#Bean("durationForX")
public Duration durationForX() {
return Duration.ofSeconds(duration1);
}
#Bean("durationForY")
public Duration durationForY() {
return Duration.ofSeconds(duration2);
}
}
Now for test create application-test.yml under src/main/resources or src/test/resources, then add the properties with test values
application-test.yml
duration1:100
duration2:500
No need of any IntegrationTestConfiguration file's you can just maintain test properties in test.yml file
Note : Make sure you annotate test class with #Profile("test") and #SpringBootTest to load the test ap[plication context with corresponding test properties
#SpringBootTest
#Profile("test)
public class AppTest {
}
My classes are..
lies in src/intregation-test/java
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplication.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
lies in src/main/java
#SpringBootApplication
public class StoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Now the problem is I wanted to exclude a class
from being get component scanned.my this class contains the dependency for apache Kafka.
if this class loads while container start up it start looking for kafka running instances.
so while running Intregation test I will not be starting my Kafka server,so I wanted to run
Intregation test making kafka shutdown.
This I can achieved by adding one line code in StoreOrderFulfillmentApplication class
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
by addding this line of code StoreOrderFulfillmentApplication class it is excluding OrderReceiveEventConfiguration class from being get component scanned.
now the problem is I not suppose add any test configuration changes in the main code.
so I am struggling to do the same exclusion from src/intregation-test/java source folder, is their some way that I can exclude this particular class during container startup code.
but it should not affect my main class code means code inside src/main/java
Any help is Appreciated..
You can make use of #Conditional as shown below.
In application.properties introduce a property say kafka.enabled.
Annotate the OrderReceiveEventConfiguration with #Conditional(PropertyCondition.class)
Depending on kafka.enabled value viz. true (for normal run) or false (for testing) the OrderReceiveEventConfiguration will be picked up or ignored respectively without changing the code.
Let know in comments in case any more information is required.
Except main #conditional annotation there are set of similar annotation to be used for different cases.
Class conditions
The #ConditionalOnClass and #ConditionalOnMissingClass annotations allows configuration to be included based on the presence or absence of specific classes.
E.g. when OObjectDatabaseTx.class is added to dependencies and there is no OrientWebConfigurer bean we create the configurer.
#Bean
#ConditionalOnWebApplication
#ConditionalOnClass(OObjectDatabaseTx.class)
#ConditionalOnMissingBean(OrientWebConfigurer.class)
public OrientWebConfigurer orientWebConfigurer() {
return new OrientWebConfigurer();
}
Bean conditions
The #ConditionalOnBean and #ConditionalOnMissingBean annotations allow a bean to be included based on the presence or absence of specific beans. You can use the value attribute to specify beans by type, or name to specify beans by name. The search attribute allows you to limit the ApplicationContext hierarchy that should be considered when searching for beans.
See the example above when we check whether there is no defined bean.
Property conditions
The #ConditionalOnProperty annotation allows configuration to be included based on a Spring Environment property. Use the prefix and name attributes to specify the property that should be checked. By default any property that exists and is not equal to false will be matched. You can also create more advanced checks using the havingValue and matchIfMissing attributes.
#ConditionalOnProperty(value='somebean.enabled', matchIfMissing = true, havingValue="yes")
#Bean
public SomeBean someBean(){
}
Resource conditions
The #ConditionalOnResource annotation allows configuration to be included only when a specific resource is present.
#ConditionalOnResource(resources = "classpath:init-db.sql")
Web application conditions
The #ConditionalOnWebApplication and #ConditionalOnNotWebApplication annotations allow configuration to be included depending on whether the application is a 'web application'.
#Configuration
#ConditionalOnWebApplication
public class MyWebMvcAutoConfiguration {...}
SpEL expression conditions
The #ConditionalOnExpression annotation allows configuration to be included based on the result of a SpEL expression.
#ConditionalOnExpression("${rest.security.enabled}==false")
You should able able to create a separate config class in your test package
#SpringBootApplication
#ActiveProfiles("Test")
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class StoreOrderFulfillmentApplicationTest {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplicationTest.class, args);
}
}
And then in your test class
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplicationTest.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
Create a test application class
#SpringBootApplication
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class TestStoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Add the following configuration annotation to you test class
#SpringApplicationConfiguration(classes = TestStoreOrderFulfillmentApplication .class)