How to override #Configuation which is present under src/main/java with #TestConfiguration during unit tests?
#Configuration
public class AppConfig {
#Bean
public EmployeeService employeeService(){
return new EmployeeService();
}
}
#Component
public class ServerStartSetup implements CommandLineRunner {
#Autowired
private EmployeeService employeeService;
public void run(String... args) {
// do something with employee service
}
}
I would like to override the above bean with some below custom bean for testing purposes.
#TestConfiguration
public class TestAppConfig {
#Bean
public EmployeeService employeeService(){
return new FakeEmployeeService();
}
}
#SpringBootTest
#Import(TestAppConfig.class)
public class UnitTest {
}
However AppConfig does not seem to be skipped. That is , it throws an error saying that there is a bean with same name employeeService. If I rename bean method name in the TestAppConfig, it injects the bean created via AppConfig.
How to fix this.?
Note: One possible solution is using #Profile. I am looking for anything other than using Profiles.
I tested locally and found that changing the method name or #Bean to #Bean("fakeEmployeeService") and adding the #Primary annotation works.
#SpringBootTest
class DemoApplicationTests {
#Autowired
private EmployeeService employeeService;
#TestConfiguration
static class TestConfig {
//#Bean("fakeEmployeeService")
#Bean
#Primary
public EmployeeService employeeServiceTest() {
return new EmployeeService() {
#Override
public void doSomething() {
System.out.println("Do something from test...");
}
};
}
}
...
}
If we want to override a bean definition in #TestConfiguration, we need:
To use the same name as the overridden bean. (Otherwise it would be an "additional" bean and we could get conflict/'d have to qualify/primary)
Since spring-boot:2.1: spring.main.allow-bean-definition-overriding=true (set this in tests ONLY!plz!)
#ref
Then, with:
#TestConfiguration
public class TestAppConfig {
#Bean // when same name, no #Primary needed
public EmployeeService employeeService(){ // same name as main bean!
return new FakeEmployeeService();
}
}
We can do that:
#Import(TestAppConfig.class)
#SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
public class UnitTest {
... // EmployeeService will be "fake", the rest is from "main config"
You can mock the AppConfig bean in your test like this:
#MockBean
private AppConfig config;
Or, like you said, just use profiles.
Related
I am trying to understand Spring/Spring-boot. My question is, can I use a Bean instantiated/declaired by #Bean to a #Autowired field? Below is my classes, what i have defined.
#SpringBootApplication
public class SpringBootTestApplication {
#Bean(name = "TestServiceInterfaceImplBean")
TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
#Autowired
public ServiceCaller serviceCaller;
public static void main(String[] args) {
ApplicationContext appContext = new
AnnotationConfigApplicationContext(SpringBootTestApplication.class);
Arrays.asList(appContext.getBeanDefinitionNames()).forEach(beanName ->
System.out.println(beanName));
SpringApplication.run(SpringBootTestApplication.class, args);
}
}
#Component()
public class ServiceCaller {
#Autowired
#Qualifier(value = "TestServiceInterfaceImplBean")
TestServiceInterface testService;
public ServiceCaller(){
System.out.println("############################### ServiceCaller");
}
}
//Service Interface
public interface TestServiceInterface {}
//Interface Implementation Class
public class TestServiceInterfaceImpl implements TestServiceInterface {
public TestServiceInterfaceImpl() {
System.out.println("############################### TestServiceInterfaceImpl");
}
}
I know by tagging #Service/#Component to TestServiceInterfaceImpl and removing #Bean and the method getTestService(), i can have #Autowire successful but i am just tyring to understand whether i can Autowire a Bean?
In this case i am getting below exception. By looking at the exception i am not able to understand where and how the loop is created.
Exception:
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| springBootTestApplication (field public com.SpringBootTestApplication.service.ServiceCaller com.SpringBootTestApplication.SpringBootTestApplication.serviceCaller)
↑ ↓
| serviceCaller (field com.SpringBootTestApplication.service.TestServiceInterface com.SpringBootTestApplication.service.ServiceCaller.testService)
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
You'd better move below part to a Configuration (#Configuration) class:
#Bean(name = "TestServiceInterfaceImplBean")
TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
#Autowired
public ServiceCaller serviceCaller;
then do the test again. And another point, for ServiceCaller, you can even define its order after the Bean of TestServiceInterfaceImplBean created.
the 2 configuration class like:
#Configuration
#AutoConfigureAfter({ MyConfiguration2.class })
public class MyConfiguration {
public MyConfiguration() {
}
#Autowired
public ServiceCaller serviceCaller;
}
#Configuration
public class MyConfiguration2 {
public MyConfiguration2() {
}
#Bean(name = "TestServiceInterfaceImplBean")
public TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
}
Using SpringBoot, I have a Component bean that is defined as #Scope("protoype"). It also injects another prototype bean
The class is defined as
#Component
#Scope("prototype")
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(DataObj data) {
this.injectedBean = beanFactory.getBean(InjectedBean.class, data)
}
}
However, IntelliJ complains about the data field on the constructor: Could not autowire. No beans of 'DataObj' type found.. But DataObj is a POJO. I pass it in at runtime in order to create the bean. Am I defining the constructor incorrectly?
Update
Had the same problem doing it this way. It still wants to treat DataObj as a bean on the factory constructor class. Doesn't matter if I annotate the class with #Component or #Configuration
#Component
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
#Bean
#Scope("prototype")
public MyClass myClass(DataObj data) {
InjectedBean injectedBean = beanFactory.getBean(InjectedBean.class, data)
return new MyClass(injectedBean);
}
}
Also tried this example from that same link:
#Configuration
public class ServiceConfig {
#Bean
public Function<DataObj, MyClass> thingFactory() {
return data-> myClass(data); //
}
#Bean
#Scope(value = "prototype")
public MyClass myClass(DataObj data) {
return new MyClass(data);
}
}
Update
I think I resolved this with some information in Spring Java Config: how do you create a prototype-scoped #Bean with runtime arguments?. Part of my problem is that I tried to put the factory bean in the Component itself, which doesn't work
In other words
#Component
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
#Bean
#Scope("prototype")
public MyClass myClass(DataObj data) {
InjectedBean injectedBean = beanFactory.getBean(InjectedBean.class, data)
return new MyClass(injectedBean);
}
}
In this cass, Spring tries to create a MyClass bean because of the #Component annotation, but another MyClass bean due to the #Bean annotation.
So I moved the #Bean to another class
#Configuration
public class ServiceConfig {
#Bean
public Function<DataObj, MyClass> thingFactory() {
return data-> myClass(data); //
}
#Bean
#Scope(value = "prototype")
public MyClass myClass(DataObj data) {
return new MyClass(data);
}
}
This appears to work, but IntelliJ still complains about DataObj. This might be an Intellij issue
My service
#Service
public class StripeServiceImpl implements StripeService {
#Override
public int getCustomerId() {
return 2;
}
}
My test
public class StripeServiceTests {
#Autowired
StripeService stripeService;
#TestConfiguration
static class TestConfig {
#Bean
public StripeService employeeService() {
return new StripeServiceImpl();
}
}
#Test
public void findCustomerByEmail_customerExists_returnCustomer() {
assertThat(stripeService.getCustomerId()).isEqualTo(2);
}
}
The error: java.lang.NullPointerException. I had checked and the stripeService is actually null.
Since you are autowiring you need an applicationcontext so that Spring can manage the bean and then can get injected in your class. Therefore you are missing an annotation to create the applicationcontext for your testclass.
I have updated your code and it works now(with junit 5 on your classpath). In the case dat you are using junit 4 it should be #RunWith(SpringRunner.class) instead of #ExtendWith(SpringExtension.class):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfiguration.class)
public class StripeServiceTests {
#Autowired
StripeService stripeService;
#TestConfiguration
static class TestConfig {
#Bean
public StripeService employeeService() {
return new StripeServiceImpl();
}
}
#Test
public void findCustomerByEmail_customerExists_returnCustomer() {
assertThat(stripeService.getCustomerId()).isEqualTo(2);
}
}
I have a main SpringBootApplication class here:
package com.example.springproj;
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
#RestController class here:
package com.example.springproj.controller;
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
#Qualifier("RefDataServiceImpl")
private RefDataService refDataService;
#GetMapping(path = {"/refdata"}, produces = {"application/json"})
public ResponseEntity<Configuration> getRefData() {
// etc
}
}
The controller autowires this interface:
package com.example.springproj.service;
public interface RefDataService {
Configuration getConfiguration(String param);
}
Which is implemented by this class:
package com.example.springproj.services;
#Service
public class RefDataServiceImpl implements RefDataService {
#Autowired
private ConfigRepository config;
#Value("${ENV}")
private String environment;
#Override
public Configuration getConfiguration(String param) {
// etc
}
}
But when I run the App.java file, I get this
***************************
APPLICATION FAILED TO START
***************************
Description:
Field refDataService in com.citi.icrm.risk.springproj.controller.RefDataController required a bean of type 'com.citi.icrm.risk.springproj.service.RefDataService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
- #org.springframework.beans.factory.annotation.Qualifier(value=RefDataServiceImpl)
Action:
Consider defining a bean of type 'com.citi.icrm.risk.springproj.service.RefDataService' in your configuration.
I'm reasonably sure that this auto-wiring should work, and I'm not sure how to go about configuring this bean in a Spring boot app. What am I doing wrong?
EDIT: Things I've already tried include:
Removing all of the #Qualifier annotations
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
private RefDataServiceImpl refDataService;
#GetMapping(path = {"/refdata"}, produces = {"application/json"})
public ResponseEntity<Configuration> getRefData() {
System.err.println("testing.");
return new ResponseEntity<Configuration>(refDataService.getConfiguration("EEMS_USER_DETAIL_URL"), HttpStatus.OK);
}
}
public class RefDataServiceImpl implements RefDataService {
#Autowired
private ConfigRepository config;
#Value("${ENV}")
private String environment;
#Override
public Configuration getConfiguration(String param) {
try {
return config.getConfiguration(param, environment);
} catch (Exception e) {
e.printStackTrace();
throw (RuntimeException) new RuntimeException().initCause(e);
}
}
}
Changing the bean names to match convention
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
#Qualifier("refDataServiceImpl")
private RefDataService refDataService;
#GetMapping(path = {"/refdata"}, produces = {"application/json"})
public ResponseEntity<Configuration> getRefData() {
System.err.println("testing.");
return new ResponseEntity<Configuration>(refDataService.getConfiguration("EEMS_USER_DETAIL_URL"), HttpStatus.OK);
}
}
#Service("refDataServiceImpl")
public class RefDataServiceImpl implements RefDataService {
#Autowired
private ConfigRepository config;
#Value("${ENV}")
private String environment;
#Override
public Configuration getConfiguration(String param) {
try {
return config.getConfiguration(param, environment);
} catch (Exception e) {
e.printStackTrace();
throw (RuntimeException) new RuntimeException().initCause(e);
}
}
}
For reference, the files fall into the app's package structure like so:
com.example.springproj
-> com.example.springproj.controller
--> RefDataController
-> com.example.springproj.services
--> RefDataService
-> com.exampple.springproj.services.impl
---> RefDataServiceImpl
Here's the folder structure, since some people have asked:
Firstly, you don't need #Qualifier("RefDataServiceImpl") if you have only one implementation of the RefDataService interface.
You just need
#Autowired
private RefDataService refDataService;
Secondly, the name of a bean generated on a class name but starts with a lowercase letter. In your example, the name of bean will look like refDataServiceImpl.
So, you can autowired this bean like below
#Autowired
#Qualifier("refDataServiceImpl")
private RefDataService refDataService;
Thirdly, you can specify the name of bean
#Service("youBeanName")
public class RefDataServiceImpl implements RefDataService
and then autowired this bean by the name in you controller, for example
#RestController
#Api("Sample")
public class RefDataController {
#Autowired
#Qualifier("youBeanName")
private RefDataService refDataService;
//....
}
Change the #Service annotation on the RefDataServiceImpl class as follows:
#Service("RefDataServiceImpl")
public class RefDataServiceImpl implements RefDataService
The #Qualifier name in the autowired service does not match a bean in your spring configuration.
The default naming convention is the full path of the class.
Because of this,
the name that Spring is probably using in your config for the RefDataServiceImpl service is this: "com.example.springproj.services.RefDataServiceImpl".
Added:
This page might be a good read: https://www.baeldung.com/spring-qualifier-annotation.
Attempt Two:
Try this
#Service
#Qualifier("RefDataServiceImpl")
#Service("RefDataServiceImpl")
public class RefDataServiceImpl implements RefDataService
I fixed the issue by putting RefDataServiceImpl in the same package as RefDataService.Before this I was keeping it in a sub-folder to the main services package. I'm still certain that I should be able to make this work with an implementation sub-folder, but this works as a solution for now.
I ran into this same issue while trying to implement a class with a database query. Adding #Repository to the top of the implemented class solved my problem.
In this #Configuration-annotated class, the #Autowired Environment class is always null.
The code-sample below is taken directly from:
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/PropertySource.html
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
#Autowired
Environment env;
#Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
// some futher contitional stuff/checks etc. on the properties
String someProp = env.getProperty(...);
if(someProp.equals(...)) {
...
}
return testBean;
}
}
If I make the class implement EnvironmentAware the Environment is set correctly (and my code works).
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig implements EnvironmentAware {
Environment env;
#Bean
public TestBean testBean() {
// ...
}
#Override
public void setEnvironment(final Environment environment) {
this.environment = environment;
}
}
Any thoughts why the #Autowired approach does not work in #Configuration-annotated classes as expected, since autowiring the Environment in other beans works.
I think you must add #Bean annotation to Environment class, because if You want #Autowired class who is not signed as #Bean then it is impossible
Try injecting values directly:
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
#Value("${testbean.name}")
private String testbeanName;
#Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(testbeanName);
return testBean;
}
}