Resolve Spring #Value expression in JUnit tests - java

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.

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

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

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

How to use spring #autowired annotation in the class having void methods?

I have an interface and service implements it. It has some void methods.
I am using spring java bean configuration. But unable to create bean object because of void methods.How to handle this problem.
I tried to use #PostConstruct instead of #Bean after reading some blogs, but it didn't work out.
public interface MyInterface {
void someData(List<MyClass> list, String somedata);
}
#Service("myInterface")
public DummyClass implements MyInterface {
public void someData(List<MyClass> list, String somedata){
// my business logic
}
}
public AppConfig {
#Bean
public MyInterface myInterface {
return new DummyClass(); // but gives error void cannot return value
}
}
My Junit looks like this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes = {AppConfig.class},
loader = AnnotationConfigContextLoader.class
)
public class MyTest {
#Autowired
DummyClass dummyClass;
// If I don't use AppConfig and simply autowire then I get
"Error creating bean name, unsatisfied dependency
}
How do I achieve dependency injection here?
Use #Configuration annotation on AppConfig class, with this all the beans defined on this class will be loaded on spring context.
If you use #Service annotation on DummyClass, you do not need to declare #Bean annotation because you are already saying to spring to detect this class for dependency injection. On the other hand use #Bean annotation to specify the instantiation of the class. Normally I let the #Bean to complex classes for dependency injection or to override configurations.

Configure #MockBean component before application start

I have a Spring Boot 1.4.2 application. Some code which is used during startup looks like this:
#Component
class SystemTypeDetector{
public enum SystemType{ TYPE_A, TYPE_B, TYPE_C }
public SystemType getSystemType(){ return ... }
}
#Component
public class SomeOtherComponent{
#Autowired
private SystemTypeDetector systemTypeDetector;
#PostConstruct
public void startup(){
switch(systemTypeDetector.getSystemType()){ // <-- NPE here in test
case TYPE_A: ...
case TYPE_B: ...
case TYPE_C: ...
}
}
}
There is a component which determines the system type. This component is used during startup from other components. In production everything works fine.
Now I want to add some integration tests using Spring 1.4's #MockBean.
The test looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
#MockBean
private SystemTypeDetector systemTypeDetectorMock;
#Before
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
#Test
public void testNrOne(){
// ...
}
}
Basically the mocking works fine. My systemTypeDetectorMock is used and if I call getSystemType -> TYPE_C is returned.
The problem is that the application doesn't start. Currently springs working order seems to be:
create all Mocks (without configuration all methods return null)
start application
call #Before-methods (where the mocks would be configured)
start test
My problem is that the application starts with an uninitialized mock. So the call to getSystemType() returns null.
My question is: How can I configure the mocks before application startup?
Edit: If somebody has the same problem, one workaround is to use #MockBean(answer = CALLS_REAL_METHODS). This calls the real component and in my case the system starts up. After startup I can change the mock behavior.
In this case you need to configure mocks in a way we used to do it before #MockBean was introduced - by specifying manually a #Primary bean that will replace the original one in the context.
#SpringBootTest
class DemoApplicationTests {
#TestConfiguration
public static class TestConfig {
#Bean
#Primary
public SystemTypeDetector mockSystemTypeDetector() {
SystemTypeDetector std = mock(SystemTypeDetector.class);
when(std.getSystemType()).thenReturn(TYPE_C);
return std;
}
}
#Autowired
private SystemTypeDetector systemTypeDetector;
#Test
void contextLoads() {
assertThat(systemTypeDetector.getSystemType()).isEqualTo(TYPE_C);
}
}
Since #TestConfiguration class is a static inner class it will be picked automatically only by this test. Complete mock behaviour that you would put into #Before has to be moved to method that initialises a bean.
I was able to fix it like this
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
// this inner class must be static!
#TestConfiguration
public static class EarlyConfiguration {
#MockBean
private SystemTypeDetector systemTypeDetectorMock;
#PostConstruct
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
}
// here we can inject the bean created by EarlyConfiguration
#Autowired
private SystemTypeDetector systemTypeDetectorMock;
#Autowired
private SomeOtherComponent someOtherComponent;
#Test
public void testNrOne(){
someOtherComponent.doStuff();
}
}
You can use the following trick:
#Configuration
public class Config {
#Bean
public BeanA beanA() {
return new BeanA();
}
#Bean
public BeanB beanB() {
return new BeanB(beanA());
}
}
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {TestConfig.class, Config.class})
public class ConfigTest {
#Configuration
static class TestConfig {
#MockBean
BeanA beanA;
#PostConstruct
void setUp() {
when(beanA.someMethod()).thenReturn(...);
}
}
}
At least it's working for spring-boot-2.1.9.RELEASE
Spring's initialization is triggered before #Before Mockito's annotation so the mock is not initialized at the time the #PostConstruct annotated method is executed.
Try to 'delay' your system detection using #Lazy annotation on the SystemTypeDetector component. Use your SystemTypeDetector where you need it, keep in mind that you cannot trigger this detection in a #PostConstruct or equivalent hook.
I think that it's due to the way you autowire your dependencies. Take a look at this (specially the part about 'Fix #1: Solve your design and make your dependencies visible'). That way you can also avoid using the #PostConstruct and just use the constructor instead.
What U are using, is good for a unit tests:
org.mockito.Mockito#when()
Try to use the following methods for mocking spring beans when the context is spined-up:
org.mockito.BDDMockito#given()
If u are using #SpyBean, then u should use another syntax:
willReturn(Arrays.asList(val1, val2))
.given(service).getEntities(any());

Autowiring Spring services into JUnit tests

Following is the service.
#Service
public class MyService {
public List<Integer> getIds(Filter filter){
// Method body
}
}
And a configuration class.
#Configuration
public static class MyApplicationContext {
#Bean
public Filter filter(ApplicationContext context) {
return new Filter();
}
}
The desired goal is a unit test to confirm getIds() returns the correct result.
See the JUnit test below.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=MyApplicationContext.class,
loader=AnnotationConfigContextLoader.class)
public class AppTest
{
#Autowired
Filter filter;
#Autowired
MyService service;
}
The compiler finds the correct bean for the Filter class but throws a BeanCreationException: Could not autowire field exception for the service variable. I've tried adding the service class to the ContextConfiguration classes attribute but that results in a IllegalStateException: Failed to load ApplicationContext exception.
How can I add MyService to ContextConfiguration?
Add the following annotation to MyApplicationContext for the service to be scanned #ComponentScan("myservice.package.name")
Add these two annotations to the test class AppTest, like in the following example:
#RunWith(SpringRunner.class )
#SpringBootTest
public class ProtocolTransactionServiceTest {
#Autowired
private ProtocolTransactionService protocolTransactionService;
}
#SpringBootTest loads the whole context.

Categories