I'm using Spring Boot. I have Service class (annotated @Service
) with a method annotated with @Retryable
.
For one of my integration test, I would like to load the Service bean to the context (with all of it's configurations and annotations).
The service has several dependencies. For example,
@Service
@EnableRetry
public class MyService{
private final BeanOne beanOne;
private final BeanTwo beanTwo;
public MyService(BeanOne beanOne, BeanTwo beanTwo){
this.beanOne = beanOne;
this.beanTwo = beanTwo
}
@Retryable(value = RuntimeException.class , maxAttempts = 3)
public void serviceAction(){
beanOne.doSomething();
beanTwo.doSomething();
doMoreThings();
}
private void doMoreThings(){
//things
//eventually throws Runtime Exception
}
}
I want to check that I indeed implemented everything regarding the retry correctly.
I'm trying to write a Spring integration test, when mocking the dependency beans, but still loading the MyService bean to the context (to check the retryable).
I tried mocking the dependency beans (using JUnit4 and Mockito):
@Import({MyService.class})
@RunWith(SpringRunner.class)
public class MyServiceIntegrationTest{
@Autowired
private MyService myService;
@MockBean
private BeanOne beanOne;
@MockBean
private BeanTwo beanTwo;
@Test
public void test(){
myService.serviceAction();
verify(beanTwo,times(3)).doSomething();
}
}
However, I'm getting NullPointerException on the beanOne.doSomething()
call. It seems like beanOne and beanTwo fields in MyServiceIntegrationTest
class are indeed mocked, but are not autowired to the actual myService
, and instead myService
's fields are null.
Note that the @Retryable
annotation indeed works (and repeats 3 times), however this is since NullPointerException is a RuntimeException. The code never actually reaches the doMoreThings()
method.
CodePudding user response:
Try to use the @SpyBean annotation instead of @MockBean for the dependencies of MyService. The @SpyBean annotation will create a spy of the bean and inject it into the application context, while still allowing you to use Mockito to stub the methods of the spy.
For example:
@Import({MyService.class})
@RunWith(SpringRunner.class)
public class MyServiceIntegrationTest{
@Autowired
private MyService myService;
@SpyBean
private BeanOne beanOne;
@SpyBean
private BeanTwo beanTwo;
@Test
public void test(){
myService.serviceAction();
verify(beanTwo,times(3)).doSomething();
}
}
This should allow you to test the MyService class with its dependencies, while still using the actual MyService instance from the application context.
CodePudding user response:
@Import
is used to load configurations you should use @SpringApplicationConfiguration
or even better the newer @SpringBootTest
annotation. @SpringApplicationConfiguration
is currently deprecated by the way.
example:
@SpringBootTest(classes = {MyService.class}) // or @SpringApplicationConfiguration with the same paramters
@RunWith(SpringRunner.class)
public class MyServiceIntegrationTest{
@Autowired
private MyService myService;
@MockBean
private BeanOne beanOne;
@MockBean
private BeanTwo beanTwo;
@Test
public void test(){
myService.serviceAction();
verify(beanTwo,times(3)).doSomething();
}
}
check:
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Import.html
- https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html
- https://docs.spring.io/spring-boot/docs/1.4.x/api/org/springframework/boot/test/SpringApplicationConfiguration.html