Mocked named beans in Spring configuration without using allow-bean-definition-overriding? - java

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 {
}

Related

SpringJUnitConfig for mulitple junit classes

In our project, every Junit class(which is annotated using SpringJunitConfig) is having a #Configuration annotated class, which creates the Bean which is required to test any particular Test-class method.
Example:
#SpringJunitConfig
class TestClass {
#Configuration
class TestConfig {
#Bean
public TestClass testClass(DependantBean dependantBean) {
return new TestClass(dependantBean);
}
#Bean
public DependantBean dependantBean() {
return new DependantBean();
}
}
#Autowire private TestClass testClass;
#Test
void testMethod() {
//do testing
}
}
However this looks handy for a single test class, but the issue is every test class is having its own configuration class, which we are trying to avoid and I wanted to have one single configuration class for my whole test classes. Can someone help me to remove this repeated #Configuration?
Thanks in advance.
You could easily create a Configuration meant only for Test cases, and it could be used in #SpringJunitConfig:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/junit/jupiter/SpringJUnitConfig.html
#SpringJUnitConfig is a composed annotation that combines
#ExtendWith(SpringExtension.class) from JUnit Jupiter with
#ContextConfiguration from the Spring TestContext Framework.
Define your TestConfig:
#Configuration
public class ValidationTestSpringConfig {
#Bean
public TaskScheduler validationTaskScheduler() {
ThreadPoolTaskScheduler tpts = new ThreadPoolTaskScheduler();
tpts.setPoolSize(2);
return tpts;
}
}
Then using this #SpringJunitConfig annotation, you can actually provide the context configuration you need, which loads up the Test Beans:
#SpringJUnitConfig(ValidationTestSpringConfig.class)
public class HttpValidationIntegrationTest {
#Autowired
private TaskScheduler taskScheduler;
}

Spring Boot - How to read properties from multiple custom yml

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

#TestPropertySource - Values not set / set as null from test properties file

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.

How do I express a dependency on a bean defined in an imported configuration in Spring?

I recently started working at a place that uses Java configuration for Spring as opposed to XML and so far I'm loving it.
My question is the following:
If we have a #Configuration annotated class A that imports another #Configuration annotated class B, what is the proper, type-safe way for a bean defined in A to depend on a bean defined in B.
Here's an example I saw in a blog (https://blog.codecentric.de/en/2012/07/spring-dependency-injection-styles-why-i-love-java-based-configuration/):
#Configuration
public class PartnerConfig {
#Bean
public PartnerService partnerService() {
return new PartnerServiceImpl();
}
}
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Autowired
private PartnerConfig partnerConfig;
#Bean
public CashingService cashingService() {
return new CashingServiceImpl(partnerConfig.partnerService());
}
}
As a second part to my question, if I was to do the above, would Spring interpret as a bean dependency? That is, when I do
partnerConfig.partnerService()
in the example above, am I getting Spring to fetch me the partnerService bean, or am I just calling a regular java method and creating a new instance of the PartherService (which is NOT what I want, since the bean should be a singleton) ?
EDIT:
It has been suggested to use a #Qualifier. Would this work?
#Configuration
public class PartnerConfig {
#Bean
#MyCustomQualifier
public PartnerService partnerService() {
return new PartnerServiceImpl();
}
}
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Bean
public CashingService cashingService(#MyCustomQualifier PartnerService partnerService) {
return new CashingServiceImpl(partnerService);
}
}
I recommend giving the docs a read: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html
Refer to the section:
#Bean Methods in #Configuration Classes
This sums it up very well.
Typically, #Bean methods are declared within #Configuration classes. In this case, bean methods may reference other #Bean methods in the same class by calling them directly. This ensures that references between beans are strongly typed and navigable.
Also take a look at: http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
Section:
Composing #Configuration classes
Just add the dependency as an argument to the #Bean annotated method and remove the autowiring of the configuration.
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Bean
public CashingService cashingService(PartnerService partnerService) {
return new CashingServiceImpl(partnerService);
}
}
or simply autowire the PartnerService instead of the configuration.
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Autowire
private PartnerService partnerService;
#Bean
public CashingService cashingService() {
return new CashingServiceImpl(partnerService);
}
}

Resolve Spring #Value expression in JUnit tests

Here's a snippet of a Spring bean:
#Component
public class Bean {
#Value("${bean.timeout:60}")
private Integer timeout;
// ...
}
Now I want to test this bean with a JUnit test. I'm therefore using the SpringJUnit4ClassRunner and the ContextConfiguration annotation.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class BeanTest {
#Autowired
private Bean bean;
// tests ...
#Configuration
public static class SpringConfiguration {
#Bean
public Bean bean() {
return new Bean();
}
}
}
Unfortunately the SpringJUnit4ClassRunner can't resolve the #Value expression, even though a default value is supplied (a NumberFormatException is thrown). It seems that the runner isn't even able to parse the expression.
Is something missing in my test?
Your test #Configuration class is missing an instance of PropertyPlaceholderConfigurer and that's why Spring does not know how to resolve those expressions; add a bean like the following to your SpringConfiguration class
#org.springframework.context.annotation.Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setIgnoreResourceNotFound(true);
return ppc;
}
and move it to a separate class and use
#ContextConfiguration(classes=SpringConfiguration.class)
to be more specific when running your test.

Categories