Spring Bean override with annotations in parent-child relationship - java

I have a two classes which are in a parent-child relation, the second one being a mock of the first.
public class A {
public void doSomething(){...};
}
public class MockA extends A {
#Override
public void doSomething() {...};
}
I also have two #Configuration classes one for development environment and one for test, the test one just mocks a couple of behaviors, but it imports the development one. Either way, I want that in test environment for MockA to be injected and in development for class A to be injected in other services which autowire it.
I can do that if I overwrite the bean in the test configuration. The following will work:
#Configuration
public class ApplicationConfig {
#Bean
public A beanA() {
return new A();
}
}
#Configuration
public class TestApplicationConfig {
#Bean
public A beanA() {
return new MockA();
}
}
However, I do not want to create the beans in the #Configuration class. I want to put the #Component annotation on each one and let them be injected in services correctly.
I've tried two approaches
1) Creating a dummy annotation, adding the annotation on the class A and trying to exclude the bean from the TestApplicationConfig.
#Configuration
#ComponentScan(
basePackages = {
"murex.connectivity.tools.interfaces.monitor.cellcomputer",
"murex.connectivity.tools.interfaces.monitor"
}, excludeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
MyAnnotation.class))
public class TestApplicationConfig {
}
2) using #Component and #Primary annotations on the MockA class. My logic being that in the case both are present (which will happen only on the test case, because only then the MockA is scanned), the MockA will be injected everywhere. But this does not happen.
I am wondering if I am doing something wrong or is this a limitation from Spring? The #Primary annotation seems to be constructed exactly for this specific case, am I mistaken? Is the fact that the two classes have a parent-child relationship that is the problem?
L.E. Using two different profiles will work. I am more curious if my understanding of the two presented approaches is correct and/or if this is a limitation on Spring using #Component annotations

Tried to combine two suggested approaches. It worked for me:
#Component
#Primary
public class ClassA {
public void doSomething() {
System.out.println("A");
}
}
#Component
public class ClassB extends ClassA {
#Override
public void doSomething() {
System.out.println("B");
}
}
#Component
public class ClassC {
#Autowired
public ClassA component;
}
All three classes are in same package.
#Configuration
#ComponentScan(value = "com.test", excludeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
Primary.class))
public class RandomConfig {
}
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {RandomConfig.class})
public class RandomTest {
#Autowired
ClassC c;
#Test
void when_then() {
//prints "B"
c.component.doSomething();
}
}
So RandomConfig excludes all #Primary beans, whereas production config uses only #Primary

You can use profiles.
#Profile("!test")
#Configuration
public class ApplicationConfig {
#Bean
public A beanA() {
return new A();
}
}
And for test cases:
in test resources in application.properties: spring.profiles.active=test or on test class #ActiveProfiles("test") and create configuration:
#Profile("test")
#Configuration
public class TestApplicationConfig {
#Bean
public A beanA() {
return new MockA();
}
}

Related

How do I put TestConfiguration in an abstract class?

I have some Spring tests that all define the same test configuration:
#TestConfiguration
public static class TestConfig {
#Bean
public EmployeeClient employeeClient(MockMvc mockMvc) {
return new EmployeeClient(mockMvc);
}
}
To reduce replication I put that code into an abstract class and had each test extend that abstract class. However, this threw Spring unresolved dependencies - it couldn't resolve EmployeeClient.
How can I make this work?
One of the ways is to make your Test class use multiple configuration files.
#Configuration
class CommonBeans {
}
#Configuration
class SpecificBeans {
}
#ContextConfiguration(classes = {CommonBeans.class, SpecificBeans.class})
public class MyAppTest {
------
}
In your test package if you are creating a configuration using #TestConfiguration instead of #Configuration then you have to use #Import annotation to make use of that.
#TestConfiguration
public class TestBeans {
}
#Import(TestBeans.class)
public class MyAppTest {
------
}

How to add a bean in SpringBootTest

The question seems extremely simple, but strangely enough I didn't find a solution.
My question is about adding/declaring a bean in a SpringBootTest, not overriding one, nor mocking one using mockito.
Here is what I got when trying the simplest implementation of my real need (but it doesn't work):
Some service, bean, and config:
#Value // lombok
public class MyService {
private String name;
}
#Value // lombok
public class MyClass {
private MyService monitoring;
}
#Configuration
public class SomeSpringConfig {
#Bean
public MyClass makeMyClass(MyService monitoring){
return new MyClass(monitoring);
}
}
The test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class })
public class SomeSpringConfigTest {
private String testValue = "testServiceName";
// this bean is not used
#Bean
public MyService monitoringService(){ return new MyService(testValue); }
// thus this bean cannot be constructed using SomeSpringConfig
#Autowired
public MyClass myClass;
#Test
public void theTest(){
assert(myClass.getMonitoring().getName() == testValue);
}
}
Now, if I replace the #Bean public MyService monitoring(){ ... } by #MockBean public MyService monitoring;, it works. I find it strange that I can easily mock a bean, but not simply provide it.
=> So how should I add a bean of my own for one test?
Edit:
I think ThreeDots's answer (create a config test class) is the general recommendation.
However, Danylo's answer (use #ContextConfiguration) fit better to what I asked, i.e. add #Bean directly in the test class.
Spring Test needs to know what configuration you are using (and hence where to scan for beans that it loads). To achieve what you want you have more options, the most basic ones are these two:
Create configuration class outside the test class that includes your bean
#Configuration
public class TestConfig {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
and then add it to to test as configuration class #SpringBootTest(classes = { SomeSpringConfig.class, TestConfig.class })
or
If you only need to use this configuration in this particular test, you can define it in static inner class
public class SomeSpringConfigTest {
#Configuration
static class ContextConfiguration {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
}
this will be automatically recognized and loaded by spring boot test
Simply add the config as
#ContextHierarchy({
#ContextConfiguration(classes = SomeSpringConfig.class)
})
What i am using in this cases is #Import:
#DataJpaTest(showSql = false)
//tests against the real data source defined in properties
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Import(value = {PersistenceConfig.class, CustomDateTimeProvider.class})
class MessageRepositoryTest extends PostgresBaseTest {
....
Here i am using a pre configured "test slice".
In this case a need to add my JpaAuditingConfig.
But why not just adding the other beans as you did with your SomeSpringConfig.class ?:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class, OtherBean.class })
public class SomeSpringConfigTest {
...
Everything listed in test will be injectable directly, all not declared must be added as mocks.

Overriding spring #Configuration in an integration test

I have a spring boot configuration class like this:
#Configuration
public class ClockConfiguration {
#Bean
public Clock getSystemClock() {
return Clock.systemUTC();
}
}
and I have some integration tests like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
}
and tests like this:
public class MiscTests extends AbstractIntegrationTest{
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
}
I want to be able to offset the clock bean to run some tests at different times on the day. How do I do this?
NOTE: I see several stack overflow answers similar to this, but I can't get them to work.
Based on other responses, it appears the solution should be something like:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#Configuration
class MyTestConfiguration {
#Bean
public Clock getSystemClock() {
Clock realClock = Clock.systemDefaultZone();
return Clock.offset(realClock, Duration.ofHours(9));
}
}
}
But nothing happens there. Do I need to #Import something? do I need to #Autowired something?
Thanks!
As you are using Spring Boot you can take advantage of the #MockBean annotation:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#MockBean
private Clock clockMock;
}
Then you can stub public methods of that bean an each of the tests accordingly and uniquely:
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
when(clockMock.getTime()).thenReturn(..);
}
As per javadoc of #MockBean:
Any existing single bean of the same type defined in the context will
be replaced by the mock.
it is the #TestConfiguration annotation that you need https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/TestConfiguration.html
#RunWith(SpringRunner.class)
public class ClockServiceImplIntegrationTest {
#TestConfiguration
static class TestOverridingClockServiceConfiguration {
#Bean
public ClockService clockService() {
return new ClockServiceImpl();
}
}
#Autowired
private ClockService clockService;
#MockBean
private ClockRepository clockRepository;
// write test cases here
}
In the case you have existing configuration you c

Spring boot: Consider defining a bean of type 'com.repository.services.interfacename' in your configuration

I've deployed an spring boot application, where i created an interface and implement it with two classes.
public interface interfacename {
LinkedHashSet<String> names(String path);
}
And implemented classes are
#Component
public class class1 implements interfacename {
......
}
#Component
public class class2 implements interfacename {
......
}
Now i try to create an instance for both the classes using interface name,
#Autowired
#Qualifier("class1")
interfacename imp1;
#Autowired
#Qualifier("class2")
interfacename imp2;
It is the configuration class,
#Configuration
public class interfacenameConfig {
#Bean
#ConditionalOnProperty(name = "class1", matchIfMissing = true)
public interfacename class1Service() {
return new class1();
}
#Bean
#ConditionalOnProperty(name = "stanfordname")
public interfacename class2Service() {
return new class2();
}
}
My Project structure is,
com.repository
application.java(#SpringApplcation)
com.repository.controller
applicationcontroller.java(#RestController)
com.repository.services
interfacename.java
interfacenameconfig.java(#configuration)
class1.java(#component)
class2.java(#component)
It throws the following error
Action:
Consider defining a bean of type 'com.repository.services.interfacename' in your configuration.
please someone guide me to solve this.
Thanks in advance
In you're usage you're saying that you want beans with the ids / names class1 and class2respectively:
#Autowired
#Qualifier("class1")
interfacename imp1;
#Autowired
#Qualifier("class2")
interfacename imp2;
But in the configuration you gave them different names:
#Configuration
public class interfacenameConfig {
#Bean
#ConditionalOnProperty(name = "class1", matchIfMissing = true)
public interfacename class1Service() {
return new class1();
}
#Bean
#ConditionalOnProperty(name = "stanfordname")
public interfacename class2Service() {
return new class2();
}
}
Namely: class1Service and class2Service. Those Ids are derived from the name of the function instantiating the beans
Two possible fixes:
Give them the names you want with #Bean("class1") and #Bean("class2").
OR
Use the names they have in the qualifier, that is: #Qualifier("class1Service") and #Qualifier("class2Service")
In your configuration class you should have an annotation to prompt for component scanning to the package that your interface interfacename belongs.
E.g.:
#ComponentScan({"com.repository.services"})
In Spring-boot you usually have this annotation in the Spring boot application class
e.g.
#SpringBootApplication
#ComponentScan({"com.repository.services"})
public class MyApplication {
}
UPDATE
If you have multiple classes implementing an interface you can use the value attribute when annotating them as #Component
#Component(value="class1")
public class class1 implements interfacename
#Component(value="class2")
public class class2 implements interfacename
and then #Autowire them with #Qualifier as you already do.
Based on your last update, since the #SpringBootApplication is in the parent directory of your spring-managed beans I think you can omit the #ComponentScan annotation. Spring will scan by default all the sub-packages below com.repository.
However I still believe that the interfacenameconfig class is redundant. Why are you declaring the same beans as the ones you have annotated as #Component? Either #Component or #Bean, there is no reason having both for the same beans as far as I know and it could probably be the source of your problem.
You need to add #Service annotation above interface implementation.
e.g. #Component
public interface interfacename {
LinkedHashSet<String> names(String path);
}
#Service
public class interfaceDefinition implements interfacename{
LinkedHashSet<String> names(String path){
// write code here
}
}
I have add the qualifier annotation along with #Component annotation. Then i ensure the application it is working fine.
#Component
#Qualifier("class1")
public class class1 implements interfacename {
......
}
Thanks for the reply

Calling a bean method from another method annotated as Bean

I have two methods annotated with #Bean. I am calling one #Bean annotated method from another. Does it mean it creates two beans of the same type?
Here's my code:
#Configuration
#Import({BaseConfig.class})
public class TestConfig{
#Autowired
BaseConfig baseconfig;
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SampleTestClass sampleTest() {
return new SampleTestClass(baseconfig.createNewBean());
}
}
#Configuration
#Import(SomeClassConfig.class)
public class BaseConfig {
#Autowired
private int someAttribute;
#Bean
public SampleTest createNewBean() {
return new SampleTest(someAttribute);
}
}
No, it wouldn't.
SampleTest has a singleton scope which is the default, so even if you call the method "directly", Spring will make sure that there is only one instance per container.
No, it doesn't. Spring automatically proxies #Configuration classes at runtime and decorates #Bean methods to provide the correct scope behavior.
However, in your case it would be cleaner not to tangle the two configurations unnecessarily. Instead, you could do this:
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SampleTestClass sampleTest(SampleTest dependency) {
return new SampleTestClass(dependency);
}

Categories

Resources