Testing a spring boot controller which has dependencies? (jUnit) - java

I have this test class :
#RunWith(SpringRunner.class)
#WebMvcTest(ClassToBeTested.class)
public class ClassToBeTestedTests {
#Autowired
private MockMvc mockMvc;
#Test
public void simpleTestMethodToGetClassWorking(){
Assert.assertTrue(true);
}
}
but in the class I want to test, I have this line :
#Autowired
AnnoyingServiceWhichIsADependency annoyingDependency;
So when I try to run the test class - I get this error :
java.lang.IllegalStateException: Failed to load ApplicationContext
and the cause by line seems to throw this up :
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ClassToBeTested': Unsatisfied dependency expressed through field 'AnnoyingServiceWhichIsADependency'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type '<package-path>.AnnoyingServiceWhichIsADependency' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I will add that the actual class does work, and does what it is meant to do, but I am having trouble making it work in the unit test world.
All help appreciated.

The reason a bean is not created for the dependency class is that you're using #WebMvcTest and not #SpringBootTest: only controllers and the MVC infrastructure classes are scanned. From the docs:
Can be used when a test focuses only on Spring MVC components.
Since it's an MVC test, you can mock the service dependency.
Example: https://reflectoring.io/spring-boot-web-controller-test/

#WebMvcTest is only going to scan the web layer- the MVC infrastructure and #Controller classes. That's it. So if your controller has some dependency to other beans from, e.g. form your service layer, they won't be found to be injected.
If you want a more comprehensive integration test, use #SpringBootTest instead of #WebMvcTest
If you want something closer to a unit test, mock your dependency.
Also note that Field injection (#Autowired directly on the field) is not recommended exactly for these reasons. I recommend you change to constructor injeciton ( add a constructor for Classtobetested and place the #Autowired annotation on it. ) Then for a unit test you can pass in a mock. Constructor injection leads to a more testable and configurable design.

Your test application context is trying to load your ClassToBeTested but is unable to find one of its dependencies and complains about it via that error. Basically you need to have a #Bean of that type in your test context. An option will be to create a TestConfig class which provides a Mock/Spy of that dependency via #Bean annotation. In your test you will have to load inside the context via the #ContextConfiguration annotation this test config you just created.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#spring-testing-annotation-contextconfiguration

Just mock that dependency. Assuming that AnnoyingServiceWhichIsADependency is an interface:
#RunWith(SpringRunner.class)
#WebMvcTest(ClassToBeTested.class)
public class ClassToBeTestedTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private AnnoyingServiceWhichIsADependency annoyingDependency;
#Test
public void simpleTestMethodToGetClassWorking(){
Assert.assertTrue(true);
}
}
Use Mockito when and thenReturn methods to instruct the mock.

Related

How to initialize bean method defined in Main class into test context

I have created a bean method in the main class:
#SpringBootApplication
#EnableScheduling
public class SpringApplication{
#Bean
Public String getCronValue(ServiceImpl service){
return service.getConfig().get("cron duration");
}
}
using this bean in a scheduled task:
#Component
public Class MySch{
#Scheduled(cron="#{getCronValue}")
public void schedulerMethod(){
//Do something
}
}
Now the problem is when I try to run JUnit tests #Bean GetCronValue is not initialized in test context and #Scheduled annotation throws an exception:
Update:-
It throws an exception:-
BeanCreationException: Error creating bean with name
'SchedulerMethod' : Initialization of bean failed; nested
exception is ' org. springframework.beans.
factory.Beanexpressio exception: Expression parsing
failed; nested exception is org. springframework.
expression.spel.SpelEvaluationException: EL1021E: A
problem occurred whilst attempting to access the
property ' getCronValue' : Error creating bean with name
'getCronValue' : Unsetisfied dependency expressed
through method 'getCronValue' parameter 0; nested
exception is org. springframework. beans. factory.
NoSuchBeanDefinitionException: No qualifying bean of
type 'com.pkg.service.ServiceImpl' available: expected at
least 1 bean which qualifies as a autowire candidate.
Dependemcy annotations: {}'
My Controller test class looks like:-
#Transactional
public class ControllerTest{
#MockBean
private Service service;
.
.
// test cases
}
How to resolve this issue.
I assume that you're using #SpringBootTest annotation.
When you test a Controller you may want narrow the tests to only the web layer by using #WebMvcTest. Any other dependencies required by the controller will be then mocked using #MockBean.
When #WebMvcTest is used Spring Boot instantiates only the web layer rather than the whole context. In an application with multiple controllers, you can even ask for only one to be instantiated for example.
#WebMvcTest(controllers =Controller.class)
public class ControllerTest{
#MockBean
private Service service;
#Autowired
private MockMvc mockMvc;
// test cases
}
I noticed that you have the #Transactional annotation in your example. This can indicate that you maybe giving too match responsibilities to your controller and may consider passing Database access related logic to a service/repostory/DAO
See https://spring.io/guides/gs/testing-web/

How can I use ActiveProfiles with Spring Boot tests with WebMvcTest?

I have a unit test that is testing a RestController using #WebMvcTest. The the controller class autowires a service class that I would like to mock. I found that I can use #Profile and #Configuration to create a config class for specifying primary beans to use when a profile is active. I tried adding an active profile to my unit test class, but it says it failed to load the ApplicationContext. I'm not sure how I can do that while using #WebMvcTest.
#ActiveProfiles("mockServices")
#RunWith(SpringRunner.class)
#WebMvcTest(VoteController.class)
public class VoteControllerTest {
...
It seems I may be approaching this wrong. Any help is appreciated.
Edit:
Here is my configuration class:
#Profile("mockService")
#Configuration
public class NotificationServiceTestConfiguration {
#Bean
#Primary
public VotingService getVotingService() {
return Mockito.mock(VotingService.class);
}
}
The error I'm actually getting is:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'VotingService'
I was able to solve it by using #MockBean for VotingService in my Unit Test class. However, I want to use the Profile configuration I have in NotificationServiceTestConfiguration without having to call out mock beans.
Any thoughts?

Unsatisfied dependency expressed through field 'fooService': No qualifying bean of type [aaa.FooService] found for dependency [aaa.FooService]

I have encountered this weird behavior of Spring in Spring boot 1.4.0. Spring basically tells me that it cannot autowire a bean to the resource, because it did not found itself for dependency.
UnsatisfiedDependencyException: Error creating bean with name 'restResource': Unsatisfied dependency expressed through field
'fooService': No qualifying bean of type [**aaa.FooService**] found for dependency [**aaa.FooService**]
FooService is autowired in the resource. When I #Autowire it into #Configuration file, which creates the resource, it is injected there as expected.
This works:
public class ServiceMocksRestConfig extends WebMvcConfigurerAdapter {
#Autowired
private FooService fooService; //instance here
#Bean
public FooResource fooResource() {
return new FooResource(); // debuger stop here
}
//Debugger step into
#RestController
public class FooResource {
#Autowired
private FooService fooService; //bang
Does someone has any idea, what might went wrong?
Funny stuff is that when I run the app from tests using boot spring runner, it also works (everything, including this resource)
I managed today to find the root cause. Its Spring Boot devtools - more precisely its split classloader (related bug: https://github.com/spring-projects/spring-boot/issues/3316)
When I put a breakpoint in ListableBeanFactory when the child REST #Configuration was about to #Autowire the FooService
and did FooService instanceof FooServiceInterface, it returned false.
And when I did FooService.class.getClassLoader() and beanfactory.getBean("fooService" /cannot use class here, would trigger not found exception/).getClass().getClassloader() these were different (one was AppClassLoader and the other was devtools restartable class loader).
Solution: remove boot devtools from classpath.
The exact reason for this is that, there is no bean initialized for type FooService inside the Spring IOC container at the moment the run-time tries to autowire FooService into ServiceMocksRestConfig .
It can be cause by different mistakes in development. This article addresses each and every possible mistakes that can cause this problem.

How to create a mock spring bean for a class having autowired dependencies

Suppose I have a class called MainClass.
public class MainClass {
#Autowired
AutoWiredClass autoWiredClass;
}
I am trying to create a mock bean of MainClass using Mockito.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class AutowiringTest {
#Configuration
static class AutowiringTestConfiguration{
#Bean
public MainClass mainClass() {
return Mockito.mock(MainClass.class);
}
}
#Autowired
MainClass mainClass;
#Test
public void testBeanCreation(){
assertNotNull(mainClass);
}
}
I am getting this error while running the test case.
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: autowiring.AutoWiredClass autowiring.MainClass.autoWiredClass; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [autowiring.AutoWiredClass] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I know I can achieve this using #Mock and #InjectMocks. But that's not the solution I want.
My requirement is to create a mock bean of MainClass without creating an actual bean of AutowiredClass. Please help me how to achieve this.
As Florian has already commented, you should try to create tests that do not need Spring at all, and you will not have those problems.
But, if there is no workaround possible, you can use a bit of magic with the AutoMockRegistryPostProcessor.
You just need to add the AutoMockRegistryPostProcessor to the #ContextConfiguration, and it will create mocks for your missing dependencies:
#ContextConfiguration(classes = { AutowiringTest.class, AutoMockRegistryPostProcessor.class })
public class AutowiringTest {
// no complains anymore, a mockito mock will be created for AutoWiredClass
The AutoMockRegistryPostProcessor class is not in maven, you will need to copy it in your project.
The docu is here.

How to add inner classes to Spring application context for Unit Testing?

I have a bean whose business logic loads beans of a certain type from the ApplicationContext in order to process them.
For my jUnit tests, I would like to create some dummy beans within my unit test class and see if my bean under test properly processes them. However, I am not sure what the best way to accomplish this is.
If I just declare my inner class within my test class, Spring will not have it as part of its application context. I realize that I could inject my application context within my jUnit class, and then use appContext.registerPrototype() to add it, however, I thought there might be a cleaner way using annotations.
I've tried to annotate the internal class with #Component, but not surprisingly, it did not work.
public class PatchEngineTest extends TestBase {
#Component
protected class Patch1 extends PatchBaseImpl implements Patch{
public void applyPatch() throws Exception {
// does nothing
}
}
#Autowired PatchRepository patchRepository;
#Autowired Patch1 patch1;
#Test
public void test() {
fail("Not yet implemented");
}
}
Not surprisingly, I get the following error message:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.ia.patch.PatchEngineTest$Patch1] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Is there any way of doing this?
You need to make your inner class static; Spring can't instantiate a non-static inner class as a bean. If there really is a valid reason why it needs to be non-static, you could create it manually in an #Bean method.
I also met this problem, and below is my solution :
#RunWith(SpringRunner.class)
#SpringBootTest
#ComponentScan
#ImportAutoConfiguration
public class FooTest {
#Component
public static class Bar {
}
}
I used spring boot 2.1.4.RELEASE

Categories

Resources