Spring why #MockBean can't autowire an interface using profile - java

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.

Related

Spring : creating beans with #Bean

I have an issue with beans creations :
#Bean
Service carService() {
return new Service(new CarRepository(), "car");
}
My issue is that CarRepository class has a dependency to EntityManager em variable (with #PersistenceContext)
So if I use new operator, I'm missing this dependency (because I'm instanciating myself the bean).
Also I have many services (CarService, BikeService etc...) and many repositories too (CarRepository, BikeRepository etc...). So using annotations directly in classes seems difficult.
So any solutions ?
Simple. Pass your repository as dependency into your Bean factory function:
#Bean
Service carService(final CarRepository carRepository) {
return new Service(carRepository, "car");
}
The repository needs to exist as a bean itself. You can create the repository bean in another bean method of a configuration class, or by annotating the class and having it created during component scanning.
I think you need to annotate every repository class with #Repository
annotation. And every service class with #Service.
In Spring you should not use the new operator for Services. Use the annotation
#Service
public classSomeClass {
or
#Component
public classSomeClass {
and your class can be injected via depnendency Injection.
If you want to create a new custom bean that can be used via dependencyInjection This is what the #Configuration annotation is for.
#Configuration
public class ConfigurationClass{
#Bean
public SomeClass createSomeClass(){
return new SomeClass();
}
}

Why is #Bean(initMethod="") not detecting given method in spring?

Edit Fixed by changing package.
I have this configuration file for spring framework
#Configuration
public class AppConfig {
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
}
JdbcAccountRepository looks like this.
#Repository
public class JdbcAccountRepository implements AccountRepository {
#Override
public Account findByAccountId(long
return new SavingAccount();
}
public void populateCache() {
System.out.println("Populating Cache");
}
public void clearCache(){
System.out.println("Clearing Cache");
}
}
I'm new to spring framework and trying to use initMethod or destroyMethod. Both of these method are showing following errors.
Caused by: org.springframework.beans.factory.support.BeanDefinitionValidationException: Could not find an init method named 'populateCache' on bean with name 'accountRepository'
Here is my main method.
public class BeanLifeCycleDemo {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
AccountRepository bean = applicationContext.getBean(AccountRepository.class);
applicationContext.close();
}
}
Edit
I was practicing from a book and had created many packages for different chapters. Error was it was importing different JdbcAccountRepository from different package that did not have that method. I fixed it and it works now. I got hinted at this from answers.
Like you said, if you are mixing configurations types, it can be confusing. Besides, even if you created a Bean of type AccountRepository, because Spring does a lot of things at runtime, it can call your initMethod, even if the compiler couldn't.
So yes, if you have many beans with the same type, Spring can be confused an know which one to call, hence your exception.
Oh and by the way, having a Configuration creating the accountRepoisitory Bean, you can remove the #Repository from your JdbcAccountRepository... It is either #Configuration + #Bean or #Component/Repository/Service + #ComponentScan.
TL;DR
Here is more information and how Spring creates your bean : What object are injected by Spring ?
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
With this code, Spring will :
Detect that you want to add a Bean in the application Context
The bean information are retrieved from the method signature. In your case, it will create a bean of type AccountRepository named accountRepository... That's all Spring knows, it won't look inside your method body.
Once Spring is done analysing your classpath, or scanning the bean definitions, it will start instanciating your object.
It will therefor creates your bean accountRepository of type AccountRepository.
But Spring is "clever" and nice with us. Even if you couldn't write this code without your compiler yelling at you, Spring can still call your method.
To make sure, try writing this code :
AccountRepository accountRepository = new JdbcAccountRepository();
accountRepository.populateCache(); // Compiler error => the method is not found.
But it works for Spring... Magic.
My recommandation, but you might thinking the same now: If you have classes across many packages to answer different business case, then rely on #Configuration classes. #ComponentScan is great to kickstart your development, but reach its limit when your application grows...
You mix two different ways of spring bean declaration:
Using #Configuration classes. Spring finds all beans annotated with #Configuration and uses them as a reference to what beans should be created.
So if you follow this path of configuration - don't use #Repository on beans. Spring will detect it anyway
Using #Repository - other way around - you don't need to use #Configuration in this case. If you decide to use #Repository put #PostConstruct annotation on the method and spring will call it, in this case remove #Configuration altogether (or at least remove #Bean method that creates JdbcAccountRepository)
Annotate populateCache method with #PostConstruct and remove initMethod from #Bean. It will work.

New bean is injected even when not calling getBean from spring context

I have a bean declared in by config file as:
#Bean
#Lazy
#Scope("prototype")
public SomeClass someClass()
{
return new SomeClass();
}
Then inside my test class I have the following code:
#Autowired
private SomeClass someClass;
#Before
public void setUp()
{
xyzObject.someMethod(someClass);
}
My question here is inside my setUp() method I am just referencing the autowired SomeClass bean but not getting it from the spring context using getBean(), but still it is creating a new bean everytime the setUp() is running before a test. Why is this happening? Does prototype scope means a new bean gets created whenever the bean's reference is used anywhere in the code? I haven't seen anything specified like that in the documentation.
That is the essence of Inversion of Control. Because you injected it into another bean, every time you instantiate another bean your injected bean would be created and provided by spring as part of instantiation of your containing bean. You don't need to call getBean() here. However if in some place you just need an instance of your SomeClass bean you may call getBean() to tell your spring environment to give you that bean to you. However, It is better to avoid such practice and rely on injection. Or create a Factory that can provide you such Bean (Still relaying on spring though) That would make your code not aware of Spring which is IMHO is more ellegant

Spring Unit test

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).

Spring / Mock components that are retrieved by using the context directly

We have a project that uses Spring annotations to configure its context.
To test this project we are using Mockito and it's Spring extension.
In tests I need to override some beans with mock/spy version.
With the #Mock/#Spy and #InjectMock annotations I have been able to use spy for beans using autowiring mechanism.
Now I have a third party component which create another Spring context and then merge the 2 contexts together. This third party component retrieve beans using a call to the context:
applicationContext.getBean(key);
In this case, the #Mock/#Spy and #InjectMock combination is not working.
The 'workaround' solution I have put in place is an XML file in which I declare my spied bean:
<mockito:spy beanName="beanToSpy"/>
To stay in the annotation world, I have tried springockito-annotations as mentioned in these similar questions:
Injecting Mockito mocks into a Spring bean
and its duplicate:
How to inject a Mock in a Spring Context
But the bean is not spied or mocked, I've probably a configuration error.
My current setup is:
A base class that is in charge of the Spring config for test:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#ContextConfiguration(loader= SpringockitoContextLoader.class, locations ={"/config.xml","/test-config.xml"})
public abstract class BaseTest {
//...
}
A test class that would like to use a mocked bean:
public class MyTest extends BaseTest {
#ReplaceWithMock #Autowired MyService myService;
#WrapWithSpy #Autowired OtherService otherService;
#Test public void someTest() {
doReturn(x).when(myService).call();
doReturn(y).when(otherService).process();
}
}
Unfortunately in MyTest, the beans are not replaced by their mock/spy versions, it is the plain old regular version.
EDIT:
The third party component that does the lookup is using its own spring parent context and add the current spring context into its own. The lookup to retrieve the component that I want to be mocked occurs after the context has been fully loaded.
What should I do to properly replace the bean in the context with a mock/spy version ?
What is wrong with the way I'm using #WrapWithSpy / #ReplaceWithMock ?
When does the call to
applicationContext.getBean(key);
happens? Is it possible that it retrieves the bean before the SpringockitoContextLoader has a chance to replace it with a spy?
One solution to stay in annotation world would be to override the bean in java config:
#Bean
public MyBeanType myBeanType() {
return spy(new MyBeanType(...))
}
The more conventional way to perform this is by simply mocking them in the test as required :
public class MyTest extends BaseTest {
#Test public void someTest() {
MyService myMockService = Mockito.mock(MyService.class);
// define when's
// perform verification
}
You can inject using SpringReflectionTestUtils, or if using setter injection just set normally.
If you use a global mocked bean in a test class with multiple tests, the results can get confusing.

Categories