When I start my application as Spring Boot application, ServiceEndpointConfig gets autowired properly. But when I run as Junit test, I get the following exception. I am using application.yml file and different profiles.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = MyServiceContextConfig.class,
loader = SpringApplicationContextLoader.class)
#ActiveProfiles({"unit", "statsd-none"})
public class MyServiceTest
{
}
#Configuration
public class MyServiceContextConfig {
#Bean
public MyService myServiceImpl(){
return new MyServiceImpl();
}
}
#Configuration
#Component
#EnableConfigurationProperties
#ComponentScan("com.myservice")
#Import({ServiceEndpointConfig.class})
public class MyServiceImpl implements MyService {
#Autowired
ServiceEndpointConfig serviceEndpointConfig;
}
#Configuration
#Component
#ConfigurationProperties(prefix="service")
public class ServiceEndpointConfig
{
}
Error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myServiceImpl':
Unsatisfied dependency expressed through field 'serviceEndpointConfig': No qualifying bean of type [com.myservice.config.ServiceEndpointConfig] found
You're handling MyServiceImpl inconsistently: On the one hand, you're using scanning annotations, and on the other, you're explicitly creating an #Bean in a configuration class. The import directive is only processed when Spring picks MyServiceImpl up via scanning; otherwise, it's not treated as a configuration.
Your relationships between the classes are tangled up; the whole point of dependency injection is that MyServiceImpl should say what sort of thing it needs but not create it itself. This organization is no better than manually creating dependencies internally.
Instead,
eliminate the #Configuration and #Import directives from MyServiceImpl,
use constructor injection on MyServiceImpl, and
change your test configuration to include all the necessary configuration classes.
With constructor injection, you may be able to bypass the Spring context entirely and run this as an actual unit test by simply creating a new MyServiceImpl(testServiceConfig).
Related
I've an interface with two implementations. Which implementaton is to be used depends of the environment (production, development, test, ...). I therefore use Spring profiles. I'm using a configuration file to instantiate the correct implementation.
#Configuration
public class BeanConfiguration {
#Profile({"develop","test-unit"})
#Bean(name = "customerEmailSender")
public CustomerEmailSender emailSenderImpl_1(){
return new EmailSenderImpl_1();
}
#Profile({"prod"})
#Bean(name = "customerEmailSender")
public CustomerEmailSender emailSenderImpl_2(){
return new EmailSenderImpl_2();
}
}
When the Spring container starts (with a specific profile), the correct bean is autowired into the class, and all works fine.
#Component
public class CustomerEmailProcessor {
#Autowire
private CustomerEmailSender customerEmailSender;
...
}
I also have a test class in which I want to autowire the bean. I'm using #Mock for autowiring.
The profile is set to "test-unit" in the test class. So, I'm expecting the spring container to lookup in the config class for the correct bean to be instantiated. But this doesn't happen.
Instead, an Exception is thrown :
Caused by: java.lang.IllegalStateException: Unable to register mock bean .... expected a single matching bean to replace but found [customerEmailSender, emailSenderImpl_1, emailSenderImpl_2]
When using #Autowire annotation, all goes fine. But of course the bean is not mocked anymore and that's what I need to have.
#RunWith(SpringRunner.class)
#ActiveProfiles(profiles = {"test-unit"})
#Import(BeanConfiguration.class)
public class CustomerEmailResourceTest {
#MockBean
private CustomerEmailSender customerEmailSender;
}
I've put a breakpoint in the config class, and I can see that when using #Autowire in the test class, the correct bean is instantiated (breaks at the line "return new EmailSenderImpl_1();".
When using #Mock, no bean at all is instantiated. Spring doesn't break at the line "return new EmailSenderImpl_1();"
Why is it that Spring can find the correct bean when using the #Mock annotation.
The #Mock annotation must be the reason that Spring doesn't use the config class "BeanConfiguration.java". That makes sence after all.
There are spring boot 2.0.2 configuration
#Configuration
public class ApiConfig {
#Bean
#Profile("!tests")
#ConditionalOnProperty(name = "enabled", havingValue = "true")
public MyService service() {
return new MyServiceImpl();
}
}
... and some controller which should be created and added to application context only if MyService bean is initialized.
#RestController
#ConditionalOnBean(MyService.class)
public class MyController {
#Autowired
private MyService service;
}
It works Ok. But occasionally spring boot skips MyController creating. According to the logs MyService is created, but after any other beans(including all controllers), at the end.
Why boot does not process #Configuration beans prior to #RestController?
Thanks.
Why boot does not process #Configuration beans prior to #Controller?
Thanks.
Because Spring doesn't guarantee that.
As well as #ConditionalOnBean warns about this kind of issue in this specification :
The condition can only match the bean definitions that have been
processed by the application context so far and, as such, it is
strongly recommended to use this condition on auto-configuration
classes only. If a candidate bean may be created by another
auto-configuration, make sure that the one using this condition runs
after.
And you don't use the annotation in an auto-configuration class. You indeed specified it in a class annotated with #RestController.
I think that to achieve your requirement you should move the #RestController bean declaration in a #Configuration class that's imported via an EnableAutoConfiguration entry in spring.factories.
It seems that #WebMvcTest and #MockBean are not working as expected. Maybe I'm missing something... I have a controller with some dependencies I'm mocking with #MockBean, but the application fails to start because it cannot find another bean that I think should not be required in this case.
CONTROLLER:
#RestController
public class ExchangeRateStoreController {
private AddExchangeRate addExchangeRate;
private AddExchangeRateRequestAdapter addExchangeRateRequestAdapter;
private GetExchangeRate getExchangeRate;
private GetExchangeRateRequestAdapter getExchangeRateRequestAdapter;
#Autowired
public ExchangeRateStoreController(ExchangeRateRepository exchangeRateRepository, ExchangeRateDateValidator exchangeRateDateValidator, ExchangeRateView exchangeRateView) {
addExchangeRate = new AddExchangeRate(exchangeRateRepository, exchangeRateDateValidator);
addExchangeRateRequestAdapter = new AddExchangeRateRequestAdapter();
getExchangeRate = new GetExchangeRate(exchangeRateView);
getExchangeRateRequestAdapter = new GetExchangeRateRequestAdapter();
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody AddExchangeRateRequest addExchangeRateRequest) {
addExchangeRate.execute(addExchangeRateRequestAdapter.toCommand(addExchangeRateRequest));
}
}
TEST:
#RunWith(SpringRunner.class)
#WebMvcTest(ExchangeRateStoreController.class)
public class ExchangeRateStoreControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
ExchangeRateRepository exchangeRateRepository;
#MockBean
ExchangeRateDateValidator exchangeRateDateValidator;
#MockBean
ExchangeRateView exchangeRateView;
#Test
public void givenValidExchangeRateCommand_whenCreate_thenOK() throws Exception {
String validRequestBody = "{\"from\":\"EUR\",\"to\":\"USD\",\"amount\":1.2345,\"date\":\"2018-11-19\"}";
doNothing().when(exchangeRateDateValidator).validate(any());
doNothing().when(exchangeRateRepository).save(any());
mvc.perform(post("/").content(validRequestBody).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated());
}
APPLICATION:
#SpringBootApplication
#EnableJpaRepositories("com...exchangerate.store.infrastructure.persistence")
#EntityScan("com...exchangerate.store.infrastructure.persistence")
#ComponentScan(basePackages = {"com...exchangerate.store.infrastructure", "com...exchangerate.store.application"} )
public class ExchangeRateStoreApplication {
public static void main(String[] args) {
SpringApplication.run(ExchangeRateStoreApplication.class, args);
}
}
And the error I get when run the test:
APPLICATION FAILED TO START
Description:
A component required a bean named 'entityManagerFactory' that could
not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your
configuration.
But, as you can see, entityManagerFactory is not a controller's dependency. So, why is the test trying to load this bean? I'm mocking all the controller dependencies, so I think it shouldn't do this.
The problem's caused by your use of #EnableJpaRepositories on your application's main class. By placing it on the main class, you're indicating that JPA repositories must always be enabled, irrespective of which particular slice of functionality you're trying to test.
You can fix your problem by doing one of the following:
Move #EnableJpaRepositores and #EntityScan onto a separate JPA-specific configuration class
Remove #EnableJpaRepositories and #EntityScan and rely on the auto-configured defaults. For this to work, your repositories and entities will have to be in a sub-package of your main class's package.
There's some more information about this in Spring Boot's reference documentation where it says the following:
If you use a test annotation to test a more specific slice of your application, you should avoid adding configuration settings that are specific to a particular area on the main method’s application class.
In this particular case, the configuration setting that is specific to a particular area is #EnableJpaRepositories.
Hello there is an excellent link on this post: EntityManagerFactory not found in SpringBoot
You could check your spring boot jpa integration and have some good tips to set up your environment.
I am trying to migrate a Spring 4.x.x to Spring boot and it has a dependency on a class in external spring 2.5 jar. I have made all the autowiring changes and below is my application class
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages = { "com.xyz" })
public class MainApiApplication {
public static void main(String[] args) {
SpringApplication.run(MainApiApplication.class, args);
}
}
The dependent class in the external jar is present under the package com.xyz.abc because of which I have placed my main application class under com.xyz package and also added the component scan under the same package
Here are my component classes with the dependency autowired
#Component
public class ComponentClassA {
#Autowired
private ComponentClassB currencyService;
}
#Component
public class ComponentClassB {
#Autowired
private DependentClass depClass;
}
DependentClass is the class present in the external dependent jar which I have locally attached and built
When building the application, compilation of all files is fine and build is generated successfully. But when I start the application, I get the below error
Field DependentClass in com.xyz.ComponentClassB required a bean of type 'com.xyz.common.util.DependentClass' that could not be found.
I don't understand the reason for the class from external jar being not found as I have added component scan for the package
The definition of DependentClass is like below
public class DependentClass extends ResourceClass<Map<String, Double>> {
// Methods and logic
}
Is it because DependentClass is extending a class ? Can someone help me figure out the reason for the error ?
The DependentClass does not have #Component annotation on it. So, you need to create a bean of DependentClass yourself either via XML or Java config.
And it is not necessary that you place your main class under the same package as DependentClass.
Define your class as per below:-
#Component("depClass")
public class DependentClass extends ResourceClass<Map<String, Double>> {
// Methods and logic
}
Component register it into your context defination if this package lie into your ScanBasePackages and the depClass inside the component annotation define the name of your bean.
you can also call it by:-
#Autowired
#Qualifier("depClass")
private DependentClass dependentClass;
If that class define in your external class then use #Bean annotaion like:-
#Bean
public DependentClass depClass(){
return new DependentClass();
}
After that Autowired the class you get the instance finally.
DependentClass is not defined in your current Spring Context.DependentClass is not annotated with a bean (#Bean).Hence nosuchbeandefinitionexception occurs.
#Bean
public class DependentClass extends ResourceClass<Map<String, Double>> {
// Methods and logic
}
I am developing OAuth implementation with Jwt tokens.
It's kind of weird but for class TokenAuthenticationService When I try to Autowired this class in a different package, I get
Consider defining a bean of type 'com.company.security.TokenAuthenticationService' in your configuration.
I did a workaround and added #Bean TokenAuthenticationService in that class.Now when I am trying to initialize an interface in the TokenAuthenticationService class, it gives the same type of error for that interface.
Consider defining a bean of type 'com.company.security.UserService' in your configuration.
ComponentScan annotation is configured like #ComponentScan({"com.company"})
What I am missing here and why?
You have two ways to define beans for autowiring in your project.
With classes defined by you, you can use the #Component annotation (or, for service classes, #Service annotation) this way:
#Service
public class TokenAuthenticationService { ... }
If you are using third party classes, you can configure them in a configuration class:
#Configuration
public MyProjectConfig {
#Bean
public ThirdPartyClass serviceClass() { new ThirdPartyClass(); }
}
(Using #Bean annotation is not a workround. You just need to understand its purpose...)
This way autowiring should work...
Pay attention to difference between #Component and #Bean annotations.